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

englefly 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 529aa6438a3 [fix](fd) Fold function projections over uniform constants 
for predicate pruning (#66906)
529aa6438a3 is described below

commit 529aa6438a3b4ec13916d3dca6a90d45a44671c4
Author: minghong <[email protected]>
AuthorDate: Mon Aug 24 20:02:25 2026 +0800

    [fix](fd) Fold function projections over uniform constants for predicate 
pruning (#66906)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    A project expression that computes a function over a uniform constant
    slot (e.g.
    `date_sub(dt, INTERVAL 1 DAY)` where `dt` is a uniform constant slot)
    did not propagate
    the resulting constant: `LogicalProject.computeUniform` only handled
    constant projects and
    bare slot aliases, so the projected slot had no uniform value and
    downstream constant
    propagation could not fold predicates over it. For a join predicate like
    `t1.dt = p.prev_dt` where `p.prev_dt` is such a projection, the
    predicate stayed
    unfolded, could not be pushed into the scan, and partition pruning
    failed.
    
    Fix:
    `LogicalProject`/`PhysicalProject`/`LogicalLoadProject.computeUniform`
    now fold a project
    expression whose input slots are all uniform constants: the constant
    values are
    substituted into the expression (new helper
    `ExpressionUtils.foldToConstantByUniformValues`) and the projected slot
    is registered as
    a uniform constant, so downstream constant propagation can fold
    predicates over it and
    push them into the scan for partition pruning.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test: FE unit test ConstantProjectionFoldingTest (the uniform constant
    source is a
    filter predicate, independent of constant CTEs; asserts the outer scan
    prunes to the
      expected single partition).
    - Behavior changed: No
    - Does this need documentation: No
---
 .../trees/plans/logical/LogicalLoadProject.java    |   7 ++
 .../trees/plans/logical/LogicalProject.java        |   7 ++
 .../trees/plans/physical/PhysicalProject.java      |   7 ++
 .../apache/doris/nereids/util/ExpressionUtils.java |  25 +++++
 .../rewrite/ConstantProjectionFoldingTest.java     | 101 +++++++++++++++++++++
 5 files changed, 147 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLoadProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLoadProject.java
index 5c2118e60e5..5de51a8a320 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLoadProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalLoadProject.java
@@ -258,6 +258,13 @@ public class LogicalLoadProject<CHILD_TYPE extends Plan> 
extends LogicalUnary<CH
                 } else if (childTrait.isUniform(slot)) {
                     builder.addUniformSlot(proj.toSlot());
                 }
+            } else {
+                // e.g. project `days_sub(begin_time, 1)` over a uniform 
constant slot `begin_time`:
+                // substitute the constant values so the projected slot also 
becomes a uniform
+                // constant, then downstream constant propagation can fold 
predicates over it.
+                Optional<Expression> constantExpr = 
ExpressionUtils.foldToConstantByUniformValues(
+                        proj.child(0), 
child(0).getLogicalProperties().getTrait());
+                constantExpr.ifPresent(expr -> 
builder.addUniformSlotAndLiteral(proj.toSlot(), expr));
             }
         }
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
index c51e614acbc..306d2c8f95d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalProject.java
@@ -332,6 +332,13 @@ public class LogicalProject<CHILD_TYPE extends Plan> 
extends LogicalUnary<CHILD_
                 } else if (childTrait.isUniform(slot)) {
                     builder.addUniformSlot(proj.toSlot());
                 }
+            } else {
+                // e.g. project `days_sub(begin_time, 1)` over a uniform 
constant slot `begin_time`:
+                // substitute the constant values so the projected slot also 
becomes a uniform
+                // constant, then downstream constant propagation can fold 
predicates over it.
+                Optional<Expression> constantExpr = 
ExpressionUtils.foldToConstantByUniformValues(
+                        proj.child(0), 
child(0).getLogicalProperties().getTrait());
+                constantExpr.ifPresent(expr -> 
builder.addUniformSlotAndLiteral(proj.toSlot(), expr));
             }
         }
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java
index dd0d25cb360..cc90d7b2103 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalProject.java
@@ -316,6 +316,13 @@ public class PhysicalProject<CHILD_TYPE extends Plan> 
extends PhysicalUnary<CHIL
                 } else if (childTrait.isUniform(slot)) {
                     builder.addUniformSlot(proj.toSlot());
                 }
+            } else {
+                // e.g. project `days_sub(begin_time, 1)` over a uniform 
constant slot `begin_time`:
+                // substitute the constant values so the projected slot also 
becomes a uniform
+                // constant, then downstream constant propagation can fold 
predicates over it.
+                Optional<Expression> constantExpr = 
ExpressionUtils.foldToConstantByUniformValues(
+                        proj.child(0), 
child(0).getLogicalProperties().getTrait());
+                constantExpr.ifPresent(expr -> 
builder.addUniformSlotAndLiteral(proj.toSlot(), expr));
             }
         }
     }
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 085c022b324..0174ff31cf0 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
@@ -26,6 +26,7 @@ import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.analyzer.Scope;
 import org.apache.doris.nereids.analyzer.UnboundSlot;
 import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.properties.DataTrait;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.rules.analysis.ExpressionAnalyzer;
 import org.apache.doris.nereids.rules.expression.ExpressionRewrite;
@@ -1409,6 +1410,30 @@ public class ExpressionUtils {
         return true;
     }
 
+    /**
+     * Try to substitute the uniform constant values of {@code childTrait} 
into {@code expr}. If all
+     * input slots of {@code expr} have a known uniform constant value in 
{@code childTrait} and the
+     * substituted expression is a constant, return it. e.g. for a project 
expression
+     * `days_sub(begin_time, 1)` over a child where `begin_time` is a uniform 
constant slot, returns
+     * `days_sub('2026-07-28 00:00:00', 1)`, so the projected slot can also be 
registered as a
+     * uniform constant and downstream constant propagation can fold 
predicates over it.
+     */
+    public static Optional<Expression> 
foldToConstantByUniformValues(Expression expr, DataTrait childTrait) {
+        Set<Slot> inputSlots = expr.getInputSlots();
+        if (inputSlots.isEmpty()) {
+            return Optional.empty();
+        }
+        Map<Expression, Expression> replaceMap = new HashMap<>();
+        for (Slot slot : inputSlots) {
+            if (!childTrait.isUniformAndHasConstValue(slot)) {
+                return Optional.empty();
+            }
+            replaceMap.put(slot, childTrait.getUniformValue(slot).get());
+        }
+        Expression constantExpr = replace(expr, replaceMap);
+        return constantExpr.isConstant() ? Optional.of(constantExpr) : 
Optional.empty();
+    }
+
     /** check constant value the expression */
     public static Optional<Literal> checkConstantExpr(Expression expr, 
Optional<ExpressionRewriteContext> context) {
         if (expr instanceof Literal) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantProjectionFoldingTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantProjectionFoldingTest.java
new file mode 100644
index 00000000000..758f76c951f
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantProjectionFoldingTest.java
@@ -0,0 +1,101 @@
+// 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.catalog.OlapTable;
+import org.apache.doris.common.FeConstants;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.utframe.TestWithFeService;
+
+import com.google.common.collect.Sets;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test that a project expression whose input slots are all uniform constants 
(e.g.
+ * `date_sub(dt, INTERVAL 1 DAY)` where `dt` is a uniform constant slot) is 
folded and the
+ * projected slot is registered as a uniform constant, so downstream constant 
propagation can
+ * fold predicates over the projected slot and partition pruning on the 
referenced tables works.
+ *
+ * <p>The uniform constant source here is a filter predicate (`WHERE dt = 
'2026-07-28'`), which
+ * is independent of constant CTEs.
+ */
+class ConstantProjectionFoldingTest extends TestWithFeService implements 
MemoPatternMatchSupported {
+
+    // the subquery projects `date_sub(dt, INTERVAL 1 DAY)`; `dt` is a uniform 
constant there
+    // (from `WHERE dt = '2026-07-28'`), so the projected slot must fold to a 
uniform constant
+    // and the outer join predicate `t1.dt = prev_dt` folds to `t1.dt = 
'2026-07-27'`, letting
+    // the outer scan prune to a single partition.
+    private static final String SQL = "SELECT * FROM cte_prune_t t1\n"
+            + "JOIN (\n"
+            + "    SELECT dt, date_sub(dt, INTERVAL 1 DAY) AS prev_dt\n"
+            + "    FROM cte_prune_t\n"
+            + "    WHERE dt = '2026-07-28'\n"
+            + ") p ON t1.dt = p.prev_dt";
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test");
+        useDatabase("test");
+        createTable("CREATE TABLE `test`.`cte_prune_t` (\n"
+                + "  `dt` DATE NULL COMMENT \"\",\n"
+                + "  `sn` VARCHAR(50) NULL COMMENT \"\",\n"
+                + "  `v` DOUBLE NULL COMMENT \"\"\n"
+                + ") DUPLICATE KEY(`dt`, `sn`)\n"
+                + "PARTITION BY RANGE(`dt`)\n"
+                + "(PARTITION p20260101 VALUES [(\"2026-01-01\"), 
(\"2026-01-02\")),\n"
+                + " PARTITION p20260726 VALUES [(\"2026-07-26\"), 
(\"2026-07-27\")),\n"
+                + " PARTITION p20260727 VALUES [(\"2026-07-27\"), 
(\"2026-07-28\")),\n"
+                + " PARTITION p20260728 VALUES [(\"2026-07-28\"), 
(\"2026-07-29\")),\n"
+                + " PARTITION p20260729 VALUES [(\"2026-07-29\"), 
(\"2026-07-30\")),\n"
+                + " PARTITION p20260901 VALUES [(\"2026-09-01\"), 
(\"2026-09-02\")))\n"
+                + "DISTRIBUTED BY HASH(`sn`) BUCKETS 3\n"
+                + "PROPERTIES('replication_num' = '1');");
+        FeConstants.runningUnitTest = true;
+    }
+
+    @Test
+    void testUniformConstantFoldThroughFunctionProjection() {
+        PlanChecker planChecker = PlanChecker.from(connectContext)
+                .analyze(SQL)
+                .rewrite();
+        Plan plan = planChecker.getCascadesContext().getRewritePlan();
+        String planString = plan.treeString();
+
+        List<LogicalOlapScan> scans = 
plan.collectToList(LogicalOlapScan.class::isInstance);
+        Assertions.assertEquals(2, scans.size(),
+                "both the outer table and the subquery should scan 
cte_prune_t, plan: " + planString);
+        Set<String> selectedPartitions = Sets.newHashSet();
+        for (LogicalOlapScan scan : scans) {
+            // the outer scan (t1) should prune to p20260727, the subquery 
scan to p20260728
+            Assertions.assertEquals(1, scan.getSelectedPartitionIds().size(),
+                    "scan on cte_prune_t should prune to exactly one 
partition, plan: " + planString);
+            selectedPartitions.add(((OlapTable) scan.getTable())
+                    
.getPartition(scan.getSelectedPartitionIds().get(0)).getName());
+        }
+        Assertions.assertEquals(Sets.newHashSet("p20260727", "p20260728"), 
selectedPartitions,
+                "outer scan should prune to p20260727 and subquery scan to 
p20260728, plan: "
+                        + planString);
+    }
+}


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

Reply via email to