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

Gabriel39 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 d6301781cab [fix](nereids) preserve date arithmetic evaluation 
boundaries (#69113)
d6301781cab is described below

commit d6301781cab1ee13d8e127a4e5c25fd04e4043bd
Author: Oliveira <[email protected]>
AuthorDate: Fri Oct 16 22:14:25 2026 +0800

    [fix](nereids) preserve date arithmetic evaluation boundaries (#69113)
    
    ### What problem does this PR solve?
    
    Issue Number: N/A (no associated GitHub issue)
    
    Related PR: #69073
    
    Problem Summary:
    
    An outer predicate on a projected date expression can be expanded and
    pushed below an inner condition. This evaluates date arithmetic on rows
    that the inner query would otherwise reject. A filtered ORC/Parquet row
    containing `9999-12-31 23:59:59` can therefore cause `WEEKS_ADD(dt, 5)`
    to raise an out-of-range error instead of returning the expected empty
    result.
    
    Preserve potentially overflowing date arithmetic aliases above lower
    Filter, Join, Limit, EXCEPT, INTERSECT, Generate and CTE consumer
    boundaries during logical and physical project predicate pushdown.
    Retain the existing volatile/non-movable expression checks. Date
    arithmetic without a lower boundary and integer zero-offset aliases
    remain eligible for pushdown, including partition pruning with an
    unrelated inner WHERE.
    
    ### Release note
    
    Fix date arithmetic overflow errors caused by predicate pushdown
    evaluating expressions on rows excluded by an inner query.
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [x] Regression test
        - [x] Unit Test
        - [x] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    <details>
    <summary>Local test results</summary>
    
    Built and deployed this branch using JDK 17 for FE and clang21 / ASAN
    for BE. Compilation, integrated FE Checkstyle, formatting, build hygiene
    and whitespace checks passed.
    
    - **FE unit tests:** 17 passed, with no failures, errors or skips:
    `DateArithmeticFilterPushDownTest` (11),
    `PushDowFilterThroughProjectTest` (4), and
    `postprocess.PushDownFilterThroughProjectTest` (2).
    - **AI review follow-up (2026-10-16):** added EXCEPT, INTERSECT and
    Generate to the existing shared date-arithmetic protection helper.
    Ordinary UNION and safe integer zero-offset pushdown remain unchanged.
    Added logical/physical tests and
    `nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary`,
    which retains a precision-lowering CAST and runs the full optimizer
    without disabling rules. Its stable-input snapshot comparison passed for
    the three rejecting boundaries, safe zero offset and expected real
    overflow. The snapshot was generated by the regression framework.
    - **Follow-up validation:** 17 FE unit tests, FE/BE build, integrated
    Checkstyle, and quick format/build-hygiene checks passed. The new P0
    suite plus `date_function_rewrite`,
    `nereids_rules_p0/cte/test_cte_filter_pushdown` and External
    `test_date_expression_filter` all passed with no failed/skipped suites
    on the verified branch build in disposable state. Source, artifact,
    process and input-stability checks passed. Original personal
    metadata/storage were preserved separately throughout validation.
    - **External regression:**
    `external_table_p0/tvf/test_date_expression_filter` passed in both
    snapshot-generation and comparison modes. Covers ORC/Parquet, five date
    functions, shared CTEs, real overflow errors, NULL, and `partitions=1/3`
    for zero-offset aliases with an unrelated inner WHERE. The output file
    was generated by the regression framework.
    - **P0 regression:** all four targeted suites passed, with no skips:
      - `doc/sql-manual/sql-functions/doc_date_error`
      - `nereids_rules_p0/cte/test_cte_filter_pushdown`
      - `query_p0/cte/test_cte_filter_pushdown`
      - `nereids_rules_p0/date_function_rewrite/date_function_rewrite`
    - **Manual replay:** ran the original query body with normal optimizer
    rules enabled against reconstructed 200-row ORC and Parquet fixtures,
    including the boundary timestamp. Both returned zero rows without
    overflow. The original remote dataset was unavailable, so these were
    reconstructed fixtures rather than the original files.
    
    Tests ran against a personal local regression configuration with HDFS
    enabled. Health checks passed before and after testing. This is targeted
    local coverage; the full TeamCity gate, full regression groups and BE
    unit tests were not run.
    
    </details>
    
    - Behavior changed:
        - [ ] No.
    - [x] Yes. Preserve date arithmetic evaluation boundaries while
    retaining safe zero-offset predicate pushdown and partition pruning.
    
    - Does this need documentation?
        - [x] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 .../post/PushDownFilterThroughProject.java         |   3 +-
 .../rewrite/PushDownFilterThroughProject.java      |   9 +-
 .../apache/doris/nereids/util/ExpressionUtils.java |  42 +++
 .../rewrite/DateArithmeticFilterPushDownTest.java  | 314 +++++++++++++++++++++
 .../tvf/test_date_expression_filter.out            |  47 +++
 .../test_date_arithmetic_rejecting_boundary.out    |  13 +
 .../tvf/test_date_expression_filter.groovy         | 107 +++++++
 .../test_date_arithmetic_rejecting_boundary.groovy |  64 +++++
 8 files changed, 594 insertions(+), 5 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
index 9b4e262d6a1..e1f317acdab 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/PushDownFilterThroughProject.java
@@ -45,7 +45,8 @@ public class PushDownFilterThroughProject extends 
PlanPostProcessor {
         PhysicalProject<? extends Plan> project = (PhysicalProject<? extends 
Plan>) child;
         Map<Slot, Expression> childAlias = project.getAliasToProducer();
         if 
(filter.getInputSlots().stream().map(childAlias::get).filter(Objects::nonNull)
-                .anyMatch(Expression::containsVolatileExpression)) {
+                .anyMatch(expression -> 
expression.containsVolatileOrNoneMovableExpression()
+                        || 
ExpressionUtils.requiresDateArithmeticProtection(expression, project.child()))) 
{
             return filter;
         }
         PhysicalFilter<? extends Plan> newFilter = 
filter.withConjunctsAndChild(
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
index 0f3478e0642..f3a791686d7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownFilterThroughProject.java
@@ -82,7 +82,7 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
         // splitConjuncts.first -> conjuncts having outer query slots which 
should NOT be pushed down
         // splitConjuncts.second -> conjuncts without any outer query slots 
which should be pushed down
         Pair<Set<Expression>, Set<Expression>> splitConjuncts =
-                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias);
+                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias, project.child());
         Set<Expression> remainPredicates = splitConjuncts.first;
         Set<Expression> pushDownPredicates = splitConjuncts.second;
         if (pushDownPredicates.isEmpty()) {
@@ -101,7 +101,7 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
         Map<Slot, Expression> childAlias = project.getAliasToProducer();
         // split the conjuncts by child's output
         Pair<Set<Expression>, Set<Expression>> splitConjuncts =
-                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias);
+                splitConjunctsByChildOutput(filter.getConjuncts(), 
childOutputs, childAlias, limit);
         Set<Expression> remainPredicates = splitConjuncts.first;
         Set<Expression> pushDownPredicates = splitConjuncts.second;
         if (pushDownPredicates.isEmpty()) {
@@ -113,7 +113,7 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
     }
 
     private static Pair<Set<Expression>, Set<Expression>> 
splitConjunctsByChildOutput(
-            Set<Expression> conjuncts, Set<Slot> childOutputs, Map<Slot, 
Expression> childAlias) {
+            Set<Expression> conjuncts, Set<Slot> childOutputs, Map<Slot, 
Expression> childAlias, Plan input) {
         Set<Expression> pushDownPredicates = Sets.newLinkedHashSet();
         Set<Expression> remainPredicates = Sets.newLinkedHashSet();
         for (Expression conjunct : conjuncts) {
@@ -125,7 +125,8 @@ public class PushDownFilterThroughProject implements 
RewriteRuleFactory {
             // `project(b + random(1, 10) as a) -> filter(b + random(1, 10) > 
1)`, it contains two distinct RANDOM.
             if (childOutputs.containsAll(conjunctSlots)
                     && 
conjunctSlots.stream().map(childAlias::get).filter(Objects::nonNull)
-                            
.noneMatch(Expression::containsVolatileOrNoneMovableExpression)) {
+                            .noneMatch(expression -> 
expression.containsVolatileOrNoneMovableExpression()
+                                    || 
ExpressionUtils.requiresDateArithmeticProtection(expression, input))) {
                 pushDownPredicates.add(conjunct);
             } else {
                 remainPredicates.add(conjunct);
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 17c766b0b70..d0e649a90e8 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
@@ -56,6 +56,8 @@ import 
org.apache.doris.nereids.trees.expressions.VolatileExpression;
 import org.apache.doris.nereids.trees.expressions.WhenClause;
 import org.apache.doris.nereids.trees.expressions.WindowExpression;
 import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
+import 
org.apache.doris.nereids.trees.expressions.functions.DateAddSubMonotonic;
+import 
org.apache.doris.nereids.trees.expressions.functions.DateCeilFloorMonotonic;
 import 
org.apache.doris.nereids.trees.expressions.functions.NoneMovableFunction;
 import org.apache.doris.nereids.trees.expressions.functions.Udf;
 import org.apache.doris.nereids.trees.expressions.functions.agg.Avg;
@@ -82,6 +84,7 @@ import 
org.apache.doris.nereids.trees.expressions.functions.scalar.Nvl;
 import 
org.apache.doris.nereids.trees.expressions.functions.scalar.UniqueFunction;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.ComparableLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
@@ -90,8 +93,16 @@ import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewri
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionVisitor;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitors;
 import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.algebra.Except;
+import org.apache.doris.nereids.trees.plans.algebra.Filter;
+import org.apache.doris.nereids.trees.plans.algebra.Generate;
+import org.apache.doris.nereids.trees.plans.algebra.Intersect;
+import org.apache.doris.nereids.trees.plans.algebra.Join;
+import org.apache.doris.nereids.trees.plans.algebra.Limit;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
 import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
 import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalCTEConsumer;
 import org.apache.doris.nereids.trees.plans.visitor.ExpressionLineageReplacer;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.VariantType;
@@ -140,6 +151,37 @@ public class ExpressionUtils {
     // folding the conjunct). We restrict MAX_MARK_SLOT_COUNT to 4 to bound 
this cost.
     private static final int MAX_MARK_SLOT_COUNT = 4;
 
+    /**
+     * Date arithmetic can throw for values outside its valid range. Keep it 
above a lower
+     * condition, join, limit, rejecting set operation, generate, or CTE 
consumer so rejected
+     * rows are not evaluated early.
+     * A date add/subtract with literal zero is an identity and remains safe 
to push through.
+     */
+    public static boolean requiresDateArithmeticProtection(Expression 
expression, Plan input) {
+        return expression.anyMatch(expr -> isUnsafeDateArithmetic((Expression) 
expr))
+                && input.anyMatch(plan -> plan instanceof Filter || plan 
instanceof Join || plan instanceof Limit
+                        || plan instanceof Except || plan instanceof Intersect 
|| plan instanceof Generate
+                        || plan instanceof LogicalCTEConsumer || plan 
instanceof PhysicalCTEConsumer);
+    }
+
+    private static boolean isUnsafeDateArithmetic(Expression expression) {
+        if (expression instanceof DateCeilFloorMonotonic) {
+            return true;
+        }
+        if (!(expression instanceof DateAddSubMonotonic)) {
+            return false;
+        }
+        DateAddSubMonotonic dateArithmetic = (DateAddSubMonotonic) expression;
+        int dateChildIndex = dateArithmetic.getMonotonicFunctionChildIndex();
+        for (int i = 0; i < expression.children().size(); i++) {
+            if (i != dateChildIndex && !(expression.child(i) instanceof 
IntegerLikeLiteral
+                    && ((IntegerLikeLiteral) 
expression.child(i)).compareTo(new IntegerLiteral(0)) == 0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Whether evaluating these expressions can produce results unsuitable 
for reuse across queries. */
     public static boolean containsNonCacheableExpression(Collection<? extends 
Expression> expressions) {
         return expressions.stream().anyMatch(expression ->
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/DateArithmeticFilterPushDownTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/DateArithmeticFilterPushDownTest.java
new file mode 100644
index 00000000000..28f861e8eb4
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/DateArithmeticFilterPushDownTest.java
@@ -0,0 +1,314 @@
+// 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.rewrite;
+
+import org.apache.doris.nereids.rules.exploration.join.JoinReorderContext;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Cast;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import org.apache.doris.nereids.trees.expressions.functions.generator.Explode;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.DaysAdd;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.HoursAdd;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.SecondCeil;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.WeeksAdd;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.YearCeil;
+import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.LimitPhase;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalExcept;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalGenerate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalIntersect;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalExcept;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalFilter;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalGenerate;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalIntersect;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalOneRowRelation;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalProject;
+import org.apache.doris.nereids.types.DateTimeV2Type;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+class DateArithmeticFilterPushDownTest {
+    private final Alias datetime = new Alias(new DateTimeV2Literal("9999-12-31 
23:59:59.000001"), "dt");
+    private final Alias id = new Alias(new IntegerLiteral(1), "id");
+    private final LogicalOneRowRelation relation = new LogicalOneRowRelation(
+            StatementScopeIdGenerator.newRelationId(), 
ImmutableList.of(datetime, id));
+
+    private List<Expression> dateExpressions() {
+        return ImmutableList.of(
+                new WeeksAdd(datetime.toSlot(), new IntegerLiteral(5)),
+                new HoursAdd(datetime.toSlot(), new IntegerLiteral(5)),
+                new SecondCeil(datetime.toSlot(), new IntegerLiteral(5)),
+                new YearCeil(datetime.toSlot(), new IntegerLiteral(5)),
+                new DaysAdd(datetime.toSlot(), new IntegerLiteral(5)));
+    }
+
+    @Test
+    void protectRejectingSetOperations() {
+        Alias rightDatetime = new Alias(new DateTimeV2Literal("2020-01-01 
00:00:00"), "right_dt");
+        Alias rightId = new Alias(new IntegerLiteral(1), "right_id");
+        LogicalOneRowRelation right = new 
LogicalOneRowRelation(StatementScopeIdGenerator.newRelationId(),
+                ImmutableList.of(rightDatetime, rightId));
+        List<Plan> children = ImmutableList.of(relation, right);
+        List<NamedExpression> setOutputs = ImmutableList.of(datetime.toSlot(), 
id.toSlot());
+        List<List<SlotReference>> outputs = ImmutableList.of(
+                ImmutableList.of((SlotReference) datetime.toSlot(), 
(SlotReference) id.toSlot()),
+                ImmutableList.of((SlotReference) rightDatetime.toSlot(), 
(SlotReference) rightId.toSlot()));
+        LogicalExcept except = new LogicalExcept(Qualifier.DISTINCT, 
setOutputs, outputs, children);
+        LogicalIntersect intersect = new LogicalIntersect(Qualifier.DISTINCT,
+                setOutputs, outputs, children);
+        PhysicalOneRowRelation source = new 
PhysicalOneRowRelation(relation.getRelationId(),
+                relation.getProjects(), relation.getLogicalProperties());
+        PhysicalOneRowRelation physicalRight = new 
PhysicalOneRowRelation(right.getRelationId(),
+                right.getProjects(), right.getLogicalProperties());
+        List<Plan> physicalChildren = ImmutableList.of(source, physicalRight);
+        assertRejectingBoundary(except, new PhysicalExcept(Qualifier.DISTINCT,
+                setOutputs, outputs, except.getLogicalProperties(), 
physicalChildren));
+        assertRejectingBoundary(intersect, new 
PhysicalIntersect(Qualifier.DISTINCT,
+                setOutputs, outputs, intersect.getLogicalProperties(), 
physicalChildren));
+    }
+
+    @Test
+    void protectGenerate() {
+        Explode explode = new Explode(new ArrayLiteral(ImmutableList.of(new 
IntegerLiteral(1))));
+        SlotReference item = new SlotReference("item", IntegerType.INSTANCE);
+        LogicalGenerate<?> generate = new 
LogicalGenerate<>(ImmutableList.of(explode),
+                ImmutableList.of(item), relation);
+        PhysicalOneRowRelation source = new 
PhysicalOneRowRelation(relation.getRelationId(),
+                relation.getProjects(), relation.getLogicalProperties());
+        assertRejectingBoundary(generate, new 
PhysicalGenerate<>(ImmutableList.of(explode),
+                ImmutableList.of(item), ImmutableList.of(), 
generate.getLogicalProperties(), source));
+    }
+
+    private void assertRejectingBoundary(Plan logicalInput, Plan 
physicalInput) {
+        for (int offset : new int[] {0, 5}) {
+            Alias result = new Alias(new Cast(new WeeksAdd(datetime.toSlot(), 
new IntegerLiteral(offset)),
+                    DateTimeV2Type.of(0)), "result");
+            Expression predicate = new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-02-05 00:00:00"));
+            LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result), logicalInput);
+            LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+            Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                    .applyTopDown(new 
PushDownFilterThroughProject()).getPlan();
+            Assertions.assertEquals(offset == 0 ? LogicalProject.class : 
LogicalFilter.class, rewritten.getClass());
+            PhysicalProject<?> physicalProject = new 
PhysicalProject<>(ImmutableList.of(result),
+                    project.getLogicalProperties(), physicalInput);
+            PhysicalFilter<?> physicalFilter = new 
PhysicalFilter<>(ImmutableSet.of(predicate),
+                    physicalProject.getLogicalProperties(), physicalProject);
+            Plan physicalRewritten = physicalFilter.accept(
+                    new 
org.apache.doris.nereids.processor.post.PushDownFilterThroughProject(), null);
+            Assertions.assertEquals(offset == 0 ? PhysicalProject.class : 
PhysicalFilter.class,
+                    physicalRewritten.getClass());
+        }
+    }
+
+    @Test
+    void keepLogicalDatePredicateAboveProject() {
+        for (Expression expression : dateExpressions()) {
+            Alias result = new Alias(new Cast(expression, 
DateTimeV2Type.of(0)), "result");
+            LogicalFilter<LogicalOneRowRelation> input = new LogicalFilter<>(
+                    ImmutableSet.of(new EqualTo(id.toSlot(), new 
IntegerLiteral(0))), relation);
+            LogicalProject<LogicalFilter<LogicalOneRowRelation>> project = new 
LogicalProject<>(
+                    ImmutableList.of(result, id.toSlot()), input);
+            Expression predicate = new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"));
+            LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+            Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                    .applyTopDown(new 
PushDownFilterThroughProject()).getPlan();
+            Assertions.assertInstanceOf(LogicalFilter.class, rewritten, 
expression.toSql());
+            Assertions.assertInstanceOf(LogicalProject.class, 
rewritten.child(0));
+        }
+    }
+
+    @Test
+    void pushZeroOffsetDatePredicateThroughLowerFilter() {
+        for (Expression zero : ImmutableList.of(new IntegerLiteral(0), new 
BigIntLiteral(0))) {
+            Alias result = new Alias(new DaysAdd(datetime.toSlot(), zero), 
"result");
+            LogicalFilter<?> input = new LogicalFilter<>(
+                    ImmutableSet.of(new EqualTo(id.toSlot(), new 
IntegerLiteral(0))), relation);
+            LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result, id.toSlot()), input);
+            Expression predicate = new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"));
+            LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+            Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                    .applyTopDown(new 
PushDownFilterThroughProject()).getPlan();
+            Assertions.assertInstanceOf(LogicalProject.class, rewritten);
+            Assertions.assertInstanceOf(LogicalFilter.class, 
rewritten.child(0));
+
+            PhysicalOneRowRelation source = new 
PhysicalOneRowRelation(relation.getRelationId(),
+                    relation.getProjects(), relation.getLogicalProperties());
+            PhysicalFilter<?> physicalInput = new 
PhysicalFilter<>(input.getConjuncts(),
+                    source.getLogicalProperties(), source);
+            PhysicalProject<?> physicalProject = new 
PhysicalProject<>(project.getProjects(),
+                    project.getLogicalProperties(), physicalInput);
+            PhysicalFilter<?> physicalFilter = new 
PhysicalFilter<>(ImmutableSet.of(predicate),
+                    physicalProject.getLogicalProperties(), physicalProject);
+            Plan physicalRewritten = physicalFilter.accept(
+                    new 
org.apache.doris.nereids.processor.post.PushDownFilterThroughProject(), null);
+            Assertions.assertInstanceOf(PhysicalProject.class, 
physicalRewritten);
+            Assertions.assertInstanceOf(PhysicalFilter.class, 
physicalRewritten.child(0));
+        }
+    }
+
+    @Test
+    void pushLogicalDatePredicateWithoutLowerFilter() {
+        for (Expression expression : 
ImmutableList.<Expression>builder().addAll(dateExpressions())
+                .add(new DaysAdd(datetime.toSlot(), new 
IntegerLiteral(0))).build()) {
+            Alias result = new Alias(new Cast(expression, 
DateTimeV2Type.of(0)), "result");
+            LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result, id.toSlot()), relation);
+            Expression predicate = new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"));
+            LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+            Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                    .applyTopDown(new 
PushDownFilterThroughProject()).getPlan();
+            Assertions.assertInstanceOf(LogicalProject.class, rewritten, 
expression.toSql());
+            Assertions.assertInstanceOf(LogicalFilter.class, 
rewritten.child(0));
+        }
+    }
+
+    @Test
+    void pushPhysicalDatePredicateWithoutLowerFilter() {
+        for (Expression expression : 
ImmutableList.<Expression>builder().addAll(dateExpressions())
+                .add(new DaysAdd(datetime.toSlot(), new 
IntegerLiteral(0))).build()) {
+            Alias result = new Alias(new Cast(expression, 
DateTimeV2Type.of(0)), "result");
+            PhysicalOneRowRelation source = new 
PhysicalOneRowRelation(relation.getRelationId(),
+                    relation.getProjects(), relation.getLogicalProperties());
+            LogicalProject<?> logicalProject = new 
LogicalProject<>(ImmutableList.of(result, id.toSlot()), relation);
+            PhysicalProject<?> project = new 
PhysicalProject<>(ImmutableList.of(result, id.toSlot()),
+                    logicalProject.getLogicalProperties(), source);
+            PhysicalFilter<?> filter = new PhysicalFilter<>(
+                    ImmutableSet.of(new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"))),
+                    project.getLogicalProperties(), project);
+            Plan rewritten = filter.accept(
+                    new 
org.apache.doris.nereids.processor.post.PushDownFilterThroughProject(), null);
+            Assertions.assertInstanceOf(PhysicalProject.class, rewritten, 
expression.toSql());
+            Assertions.assertInstanceOf(PhysicalFilter.class, 
rewritten.child(0));
+        }
+    }
+
+    @Test
+    void keepLogicalDatePredicateAboveJoinOrLimit() {
+        LogicalOneRowRelation right = new LogicalOneRowRelation(
+                StatementScopeIdGenerator.newRelationId(), 
ImmutableList.of(new Alias(new IntegerLiteral(1), "right")));
+        for (Plan input : ImmutableList.of(
+                new LogicalJoin<>(JoinType.INNER_JOIN, relation, right, new 
JoinReorderContext()),
+                new LogicalLimit<>(0, 0, LimitPhase.ORIGIN, relation))) {
+            Alias result = new Alias(new WeeksAdd(datetime.toSlot(), new 
IntegerLiteral(5)), "result");
+            LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result), input);
+            LogicalFilter<?> filter = new LogicalFilter<>(ImmutableSet.of(
+                    new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"))), project);
+            Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                    .applyTopDown(new 
PushDownFilterThroughProject()).getPlan();
+            Assertions.assertInstanceOf(LogicalFilter.class, rewritten);
+            Assertions.assertInstanceOf(LogicalProject.class, 
rewritten.child(0));
+        }
+    }
+
+    @Test
+    void keepLogicalDatePredicateAboveLimitProject() {
+        Alias result = new Alias(new WeeksAdd(datetime.toSlot(), new 
IntegerLiteral(5)), "result");
+        LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result), relation);
+        LogicalLimit<?> limit = new LogicalLimit<>(0, 0, LimitPhase.ORIGIN, 
project);
+        LogicalFilter<?> filter = new LogicalFilter<>(ImmutableSet.of(
+                new EqualTo(result.toSlot(), new DateTimeV2Literal("2020-01-01 
00:00:00"))), limit);
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                .applyTopDown(new PushDownFilterThroughProject()).getPlan();
+        Assertions.assertInstanceOf(LogicalFilter.class, rewritten);
+        Assertions.assertInstanceOf(LogicalLimit.class, rewritten.child(0));
+    }
+
+    @Test
+    void keepDatePredicateAboveCteConsumer() {
+        LogicalCTEConsumer consumer = new 
LogicalCTEConsumer(StatementScopeIdGenerator.newRelationId(),
+                StatementScopeIdGenerator.newCTEId(), "c", relation);
+        Alias result = new Alias(new WeeksAdd(consumer.getOutput().get(0), new 
IntegerLiteral(5)), "result");
+        LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result), consumer);
+        Expression predicate = new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"));
+        LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                .applyTopDown(new PushDownFilterThroughProject()).getPlan();
+        Assertions.assertInstanceOf(LogicalFilter.class, rewritten);
+        Assertions.assertInstanceOf(LogicalProject.class, rewritten.child(0));
+
+        PhysicalCTEConsumer physicalConsumer = new 
PhysicalCTEConsumer(consumer.getRelationId(), consumer.getCteId(),
+                consumer.getConsumerToProducerOutputMap(), 
consumer.getProducerToConsumerOutputMap(),
+                consumer.getLogicalProperties());
+        PhysicalProject<?> physicalProject = new 
PhysicalProject<>(ImmutableList.of(result),
+                project.getLogicalProperties(), physicalConsumer);
+        PhysicalFilter<?> physicalFilter = new 
PhysicalFilter<>(ImmutableSet.of(predicate),
+                physicalProject.getLogicalProperties(), physicalProject);
+        Plan physicalRewritten = physicalFilter.accept(
+                new 
org.apache.doris.nereids.processor.post.PushDownFilterThroughProject(), null);
+        Assertions.assertInstanceOf(PhysicalFilter.class, physicalRewritten);
+        Assertions.assertInstanceOf(PhysicalProject.class, 
physicalRewritten.child(0));
+    }
+
+    @Test
+    void stillPushLogicalPassthroughPredicate() {
+        Alias result = new Alias(new WeeksAdd(datetime.toSlot(), new 
IntegerLiteral(5)), "result");
+        LogicalProject<?> project = new 
LogicalProject<>(ImmutableList.of(result, id.toSlot()), relation);
+        Expression predicate = new EqualTo(id.toSlot(), new IntegerLiteral(0));
+        LogicalFilter<?> filter = new 
LogicalFilter<>(ImmutableSet.of(predicate), project);
+        Plan rewritten = 
PlanChecker.from(MemoTestUtils.createConnectContext(), filter)
+                .applyTopDown(new PushDownFilterThroughProject()).getPlan();
+        Assertions.assertInstanceOf(LogicalProject.class, rewritten);
+        Assertions.assertInstanceOf(LogicalFilter.class, rewritten.child(0));
+    }
+
+    @Test
+    void keepPhysicalDatePredicateAboveProject() {
+        for (Expression expression : dateExpressions()) {
+            Alias result = new Alias(new Cast(expression, 
DateTimeV2Type.of(0)), "result");
+            PhysicalOneRowRelation source = new 
PhysicalOneRowRelation(relation.getRelationId(),
+                    relation.getProjects(), relation.getLogicalProperties());
+            PhysicalFilter<?> input = new PhysicalFilter<>(
+                    ImmutableSet.of(new EqualTo(id.toSlot(), new 
IntegerLiteral(0))),
+                    source.getLogicalProperties(), source);
+            LogicalProject<?> logicalProject = new 
LogicalProject<>(ImmutableList.of(result, id.toSlot()), relation);
+            PhysicalProject<?> project = new 
PhysicalProject<>(ImmutableList.of(result, id.toSlot()),
+                    logicalProject.getLogicalProperties(), input);
+            PhysicalFilter<?> filter = new PhysicalFilter<>(
+                    ImmutableSet.of(new EqualTo(result.toSlot(), new 
DateTimeV2Literal("2020-01-01 00:00:00"))),
+                    project.getLogicalProperties(), project);
+            Plan rewritten = filter.accept(
+                    new 
org.apache.doris.nereids.processor.post.PushDownFilterThroughProject(), null);
+            Assertions.assertInstanceOf(PhysicalFilter.class, rewritten, 
expression.toSql());
+            Assertions.assertInstanceOf(PhysicalProject.class, 
rewritten.child(0));
+        }
+    }
+}
diff --git 
a/regression-test/data/external_table_p0/tvf/test_date_expression_filter.out 
b/regression-test/data/external_table_p0/tvf/test_date_expression_filter.out
new file mode 100644
index 00000000000..7bc7653a638
--- /dev/null
+++ b/regression-test/data/external_table_p0/tvf/test_date_expression_filter.out
@@ -0,0 +1,47 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !valid_rows --
+2020-02-05 00:00:00    1
+
+-- !shared_cte --
+2020-02-05 00:00:00
+2020-02-05 00:00:00
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !filtered_date --
+
+-- !valid_rows --
+2020-02-05 00:00:00    1
+
+-- !shared_cte --
+2020-02-05 00:00:00
+2020-02-05 00:00:00
+
+-- !partition_pruning --
+2
+
+-- !partition_pruning --
+1
+
+-- !zero_offset_partition_pruning --
+2
+
+-- !null --
+\N
+
diff --git 
a/regression-test/data/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.out
 
b/regression-test/data/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.out
new file mode 100644
index 00000000000..fe7329764f8
--- /dev/null
+++ 
b/regression-test/data/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.out
@@ -0,0 +1,13 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !except --
+2020-02-05T00:00
+
+-- !intersect --
+2020-02-05T00:00
+
+-- !generate --
+2020-02-05T00:00
+
+-- !zero_offset --
+2020-01-01T00:00
+
diff --git 
a/regression-test/suites/external_table_p0/tvf/test_date_expression_filter.groovy
 
b/regression-test/suites/external_table_p0/tvf/test_date_expression_filter.groovy
new file mode 100644
index 00000000000..9e453ec3124
--- /dev/null
+++ 
b/regression-test/suites/external_table_p0/tvf/test_date_expression_filter.groovy
@@ -0,0 +1,107 @@
+// 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.
+
+suite("test_date_expression_filter", "p0,external,hive,external_docker") {
+    if 
(!context.config.otherConfigs.get("enableHiveTest")?.equalsIgnoreCase("true")) {
+        logger.info("Hive external tests are disabled")
+        return
+    }
+    def host = context.config.otherConfigs.get("externalEnvIp")
+    def port = context.config.otherConfigs.get("hive2HdfsPort")
+    def defaultFS = "hdfs://${host}:${port}"
+    def rows = """SELECT CAST(1 AS INT) AS id,
+                        CAST('9999-12-31 23:59:59' AS DATETIME(6)) AS dt,
+                        CAST('2025-01-01' AS DATETIME(6)) AS filter_dt
+                  UNION ALL SELECT 2, CAST('2020-01-01' AS DATETIME(6)),
+                                       CAST('2020-01-01' AS DATETIME(6))"""
+    sql "set short_circuit_evaluation=true"
+    for (def format : ["orc", "parquet"]) {
+        def path = 
"${defaultFS}/user/doris/tmp_data/test_date_expression_filter/${UUID.randomUUID()}/exp_"
+        def exported = sql """SELECT * FROM (${rows}) AS input
+            INTO OUTFILE "${path}" FORMAT AS ${format}
+            PROPERTIES("fs.defaultFS"="${defaultFS}", 
"hadoop.username"="doris")"""
+        def source = """HDFS("uri"="${exported[0][3]}",
+                             "hadoop.username"="doris", 
"format"="${format}")"""
+        for (def function : ["WEEKS_ADD", "HOURS_ADD", "SECOND_CEIL", 
"YEAR_CEIL", "DAYS_ADD"]) {
+            // The outer predicate must not evaluate a partial date function 
before the inner WHERE.
+            order_qt_filtered_date """WITH cte1 AS (
+                SELECT CAST(${function}(dt,5) AS DATETIME) AS d,
+                       APPROX_COUNT_DISTINCT(id) AS n
+                FROM ${source}
+                WHERE filter_dt <= (SELECT MIN('2024-07-01') FROM ${source})
+                  AND CAST(WEEKS_SUB(filter_dt,0) AS DATETIME)='2012-05-05 
11:11:11'
+                GROUP BY d ORDER BY d,n
+            ), cte2 AS (SELECT * FROM cte1 WHERE d='2019-10-23 16:50:42')
+            SELECT * FROM cte2"""
+        }
+        order_qt_valid_rows """WITH cte AS (
+            SELECT CAST(WEEKS_ADD(dt,5) AS DATETIME) AS d, 
APPROX_COUNT_DISTINCT(id) AS n
+            FROM ${source} WHERE filter_dt='2020-01-01' GROUP BY d
+        ) SELECT DATE_FORMAT(d,'%Y-%m-%d %H:%i:%s'),n FROM cte WHERE 
d='2020-02-05'"""
+        // Shared CTE predicates may be collected and pushed back into a 
producer with a hidden WHERE.
+        def inlineThreshold = sql("SHOW VARIABLES LIKE 
'inline_cte_referenced_threshold'")[0][1]
+        sql "set inline_cte_referenced_threshold=0"
+        try {
+            def branch = """SELECT DATE_FORMAT(d,'%Y-%m-%d %H:%i:%s') FROM (
+                SELECT CAST(WEEKS_ADD(dt,5) AS DATETIME) AS d FROM 
filtered_input
+            ) s WHERE d='2020-02-05'"""
+            def sharedCte = """WITH filtered_input AS (
+                SELECT dt FROM ${source} WHERE filter_dt='2020-01-01'
+            ) ${branch} UNION ALL ${branch}"""
+            order_qt_shared_cte sharedCte
+        } finally {
+            sql "set inline_cte_referenced_threshold=${inlineThreshold}"
+        }
+        // Overflow on a row that actually participates in date arithmetic 
must still be reported.
+        test {
+            sql "SELECT WEEKS_ADD(dt,5) FROM ${source}"
+            exception "Operation week_add of 9999-12-31 23:59:59, 5 out of 
range"
+        }
+    }
+    sql "DROP TABLE IF EXISTS test_date_expression_filter_partition_pruning"
+    // Explicit partitions make the safe alias predicate's pruning boundary 
observable.
+    sql """CREATE TABLE test_date_expression_filter_partition_pruning (dt DATE 
NOT NULL, id INT)
+        DUPLICATE KEY(dt, id)
+        PARTITION BY RANGE(dt) (
+            PARTITION p1 VALUES [('2020-01-01'), ('2020-01-02')),
+            PARTITION p2 VALUES [('2020-01-02'), ('2020-01-03')),
+            PARTITION p3 VALUES [('2020-01-03'), ('2020-01-04'))
+        )
+        DISTRIBUTED BY HASH(dt) BUCKETS 3
+        PROPERTIES("replication_num"="1")"""
+    sql "INSERT INTO test_date_expression_filter_partition_pruning VALUES 
('2020-01-01',1),('2020-01-02',2),('2020-01-03',3)"
+    for (def offset : [0, 1]) {
+        def query = """SELECT id FROM (
+            SELECT DAYS_ADD(dt,${offset}) AS d,id FROM 
test_date_expression_filter_partition_pruning
+        ) s WHERE d='2020-01-02'"""
+        explain {
+            sql query
+            contains "partitions=1/3 (p${2 - offset})"
+        }
+        order_qt_partition_pruning query
+    }
+    // A zero-offset alias stays safe even with an unrelated lower WHERE.
+    def zeroOffsetQuery = """SELECT id FROM (
+        SELECT DAYS_ADD(dt,0) AS d,id FROM 
test_date_expression_filter_partition_pruning WHERE id > 0
+    ) s WHERE d='2020-01-02'"""
+    explain {
+        sql zeroOffsetQuery
+        contains "partitions=1/3 (p2)"
+    }
+    order_qt_zero_offset_partition_pruning zeroOffsetQuery
+    order_qt_null "SELECT WEEKS_ADD(CAST(NULL AS DATETIME),5)"
+}
diff --git 
a/regression-test/suites/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.groovy
 
b/regression-test/suites/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.groovy
new file mode 100644
index 00000000000..983d9f260a6
--- /dev/null
+++ 
b/regression-test/suites/nereids_rules_p0/filter_push_down/test_date_arithmetic_rejecting_boundary.groovy
@@ -0,0 +1,64 @@
+// 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.
+
+suite("test_date_arithmetic_rejecting_boundary") {
+    sql "DROP TABLE IF EXISTS test_date_boundary_input"
+    sql "DROP TABLE IF EXISTS test_date_boundary_right"
+    sql """CREATE TABLE test_date_boundary_input (id INT, dt DATETIME(6), arr 
ARRAY<INT>)
+        DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES("replication_num"="1")"""
+    sql """CREATE TABLE test_date_boundary_right (dt DATETIME(6))
+        DUPLICATE KEY(dt) DISTRIBUTED BY HASH(dt) BUCKETS 1
+        PROPERTIES("replication_num"="1")"""
+    sql """INSERT INTO test_date_boundary_input VALUES
+        (1,'9999-12-31 23:59:59.000001',[]), (2,'2020-01-01 
00:00:00.000001',[1])"""
+    sql "INSERT INTO test_date_boundary_right VALUES ('9999-12-31 
23:59:59.000001')"
+
+    // The cast narrows precision so inverse date comparison rewriting cannot 
hide early evaluation.
+    // No optimizer rule is disabled: EXCEPT must reject the overflowing row 
before arithmetic.
+    order_qt_except """SELECT d FROM (
+        SELECT CAST(WEEKS_ADD(dt,5) AS DATETIME(0)) AS d FROM (
+            SELECT dt FROM test_date_boundary_input
+            EXCEPT SELECT dt FROM test_date_boundary_right
+        ) difference
+    ) s WHERE d='2020-02-05'"""
+
+    sql "TRUNCATE TABLE test_date_boundary_right"
+    sql "INSERT INTO test_date_boundary_right VALUES ('2020-01-01 
00:00:00.000001')"
+    order_qt_intersect """SELECT d FROM (
+        SELECT CAST(WEEKS_ADD(dt,5) AS DATETIME(0)) AS d FROM (
+            SELECT dt FROM test_date_boundary_input
+            INTERSECT SELECT dt FROM test_date_boundary_right
+        ) common_rows
+    ) s WHERE d='2020-02-05'"""
+
+    // Non-outer explode drops the overflowing row because its array is empty.
+    order_qt_generate """SELECT d FROM (
+        SELECT CAST(WEEKS_ADD(dt,5) AS DATETIME(0)) AS d
+        FROM test_date_boundary_input LATERAL VIEW explode(arr) lv AS item
+    ) s WHERE d='2020-02-05'"""
+
+    // Identity date arithmetic and ordinary aliases remain pushable at these 
boundaries.
+    order_qt_zero_offset """SELECT d FROM (
+        SELECT CAST(WEEKS_ADD(dt,0) AS DATETIME(0)) AS d
+        FROM test_date_boundary_input LATERAL VIEW explode(arr) lv AS item
+    ) s WHERE d='2020-01-01'"""
+    test {
+        sql "SELECT WEEKS_ADD(dt,5) FROM test_date_boundary_input"
+        exception "out of range"
+    }
+}


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

Reply via email to