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 56d35ef4858 [fix](fd) Propagate constants out of one row relation to 
enable partition pruning (#66905)
56d35ef4858 is described below

commit 56d35ef485812eaf72c6f47874503bf404fc3a54
Author: minghong <[email protected]>
AuthorDate: Tue Aug 25 19:24:13 2026 +0800

    [fix](fd) Propagate constants out of one row relation to enable partition 
pruning (#66905)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    A CTE that defines a single constant row (e.g. `WITH params AS (SELECT
    CAST(...) AS
    begin_time, ...)`) is inlined into its consumers as a one-row relation,
    but the constant
    projects were only registered as uniform slots without values. Constant
    propagation and
    predicate inference outside the relation therefore could not substitute
    the constant
    values into predicates over the CTE columns, so predicates like
    `dt BETWEEN DATE_SUB(params.begin_time, INTERVAL params.period_days DAY)
    AND
    DATE_SUB(params.begin_time, INTERVAL 1 DAY)` were not folded, the `dt`
    predicates were
    left as nested-loop-join conjuncts with runtime filters, and partition
    pruning failed:
    the scan read all 526/537 partitions instead of the single needed
    partition.
    
    Fix:
    `LogicalOneRowRelation.computeUniform` now registers the values of
    constant projects as
    uniform constants, so constant propagation can substitute them into
    predicates over the
    CTE columns, fold functions such as DATE_SUB over them, and push the
    resulting
    predicates into the scan for partition pruning.
---
 .../trees/plans/logical/LogicalOneRowRelation.java |  13 ++-
 .../nereids/rules/rewrite/ConstantCteTest.java     | 121 +++++++++++++++++++++
 .../rules/rewrite/PruneNestedColumnTest.java       |   9 +-
 .../infer_predicate/pull_up_predicate_literal.out  |   4 +-
 .../cte/test_constant_cte_partition_prune.groovy   |  80 ++++++++++++++
 5 files changed, 221 insertions(+), 6 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
index 370fa71df4b..e1d3f6394fd 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalOneRowRelation.java
@@ -174,7 +174,18 @@ public class LogicalOneRowRelation extends LogicalRelation 
implements OneRowRela
 
     @Override
     public void computeUniform(DataTrait.Builder builder) {
-        getOutput().forEach(builder::addUniformSlot);
+        for (NamedExpression project : getProjects()) {
+            if (project instanceof Alias && project.child(0).isConstant()) {
+                // A one row relation's constant projects are known 
literals/constant expressions,
+                // register the value so that constant propagation and 
predicate inference outside
+                // the relation can fold predicates over these slots (e.g. a 
constant CTE whose
+                // consumers reference `date_sub(params.begin_time, ...)`), 
which enables
+                // partition pruning on the referenced tables.
+                builder.addUniformSlotAndLiteral(project.toSlot(), 
project.child(0));
+            } else {
+                builder.addUniformSlot(project.toSlot());
+            }
+        }
     }
 
     @Override
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantCteTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantCteTest.java
new file mode 100644
index 00000000000..cc6242a0fc6
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantCteTest.java
@@ -0,0 +1,121 @@
+// 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 CTE defining a constant single row (e.g. `params`) propagates 
the constants
+ * out, so that downstream predicates over the CTE columns can be 
constant-folded and the
+ * partition pruning on the referenced tables works.
+ *
+ * <p>After the constant CTE is inlined, the first consumer predicate
+ * `dt BETWEEN params.begin_time AND params.end_time` is folded and the scan 
prunes to the
+ * day partition; the second consumer predicate
+ * `dt BETWEEN DATE_SUB(params.begin_time, INTERVAL params.period_days DAY)
+ *        AND DATE_SUB(params.begin_time, INTERVAL 1 DAY)`
+ * was not folded, so the scan read all partitions.
+ */
+class ConstantCteTest extends TestWithFeService implements 
MemoPatternMatchSupported {
+
+    // the constant CTE `params` participates in two joins; the second join 
uses
+    // DATE_SUB() over the CTE columns, which needs constant folding to prune 
partitions.
+    private static final String SQL = "WITH params AS (\n"
+            + "    SELECT\n"
+            + "        CAST('2026-07-28 00:00:00' AS DATETIME) AS 
begin_time,\n"
+            + "        CAST('2026-07-28 23:59:59' AS DATETIME) AS end_time,\n"
+            + "        DATEDIFF(CAST('2026-07-28 23:59:59' AS DATETIME), "
+            + "CAST('2026-07-28 00:00:00' AS DATETIME)) + 1 AS period_days\n"
+            + "),\n"
+            + "current_data AS (\n"
+            + "    SELECT SUM(v) AS total_value\n"
+            + "    FROM cte_prune_t\n"
+            + "    JOIN params ON 1=1\n"
+            + "    WHERE dt BETWEEN params.begin_time AND params.end_time\n"
+            + "),\n"
+            + "last_period_data AS (\n"
+            + "    SELECT SUM(v) AS total_value\n"
+            + "    FROM cte_prune_t\n"
+            + "    JOIN params ON 1=1\n"
+            + "    WHERE dt BETWEEN DATE_SUB(params.begin_time, INTERVAL 
params.period_days DAY)\n"
+            + "               AND DATE_SUB(params.begin_time, INTERVAL 1 
DAY)\n"
+            + ")\n"
+            + "SELECT * FROM current_data, last_period_data";
+
+    @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 testConstantCteFoldJoinPredicateAndPrunePartition() {
+        // params has 2 consumers; force inline to match the reported scenario
+        connectContext.getSessionVariable().inlineCTEReferencedThreshold = 2;
+
+        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 current_data and last_period_data should scan 
cte_prune_t, plan: " + planString);
+        Set<String> selectedPartitions = Sets.newHashSet();
+        for (LogicalOlapScan scan : scans) {
+            // current_data only needs p20260728, last_period_data only needs 
p20260727;
+            // both must prune to exactly one partition
+            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,
+                "current_data should prune to p20260728 and last_period_data 
to p20260727, plan: "
+                        + planString);
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
index ca9c59067c6..c78c2bc85e6 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneNestedColumnTest.java
@@ -879,9 +879,12 @@ public class PruneNestedColumnTest extends 
TestWithFeService implements MemoPatt
                                         logicalOlapScan()
                                     )
                                 ).when(p -> {
-                                    Assertions.assertEquals(2, 
p.getProjects().size());
-                                    
Assertions.assertTrue(p.getProjects().stream()
-                                            .anyMatch(o -> o instanceof Alias 
&& o.child(0) instanceof ElementAt));
+                                    // the one-row relation's constant `id` is 
propagated into the
+                                    // left side (`id = 100` pushed into the 
filter below), so the
+                                    // project above the filter only keeps the 
pushed-down access
+                                    Assertions.assertEquals(1, 
p.getProjects().size());
+                                    
Assertions.assertTrue(p.getProjects().get(0) instanceof Alias
+                                            && p.getProjects().get(0).child(0) 
instanceof ElementAt);
                                     return true;
                                 }),
                                 logicalOneRowRelation()
diff --git 
a/regression-test/data/nereids_rules_p0/infer_predicate/pull_up_predicate_literal.out
 
b/regression-test/data/nereids_rules_p0/infer_predicate/pull_up_predicate_literal.out
index e352e38da3a..52dda0fd519 100644
--- 
a/regression-test/data/nereids_rules_p0/infer_predicate/pull_up_predicate_literal.out
+++ 
b/regression-test/data/nereids_rules_p0/infer_predicate/pull_up_predicate_literal.out
@@ -2,7 +2,7 @@
 -- !test_pull_up_literal1 --
 PhysicalResultSink
 --PhysicalProject
-----hashJoin[INNER_JOIN] hashCondition=((col1 = ds.col1) and (col2 = ds.col2)) 
otherCondition=()
+----NestedLoopJoin[INNER_JOIN]
 ------PhysicalOneRowRelation
 ------filter((ds.col1 = 'abc') and (ds.col2 = 'def'))
 --------PhysicalOlapScan[test_pull_up_predicate_literal(ds)]
@@ -10,7 +10,7 @@ PhysicalResultSink
 -- !test_pull_up_literal2 --
 PhysicalResultSink
 --PhysicalProject
-----hashJoin[INNER_JOIN] hashCondition=((col1 = ds.col1) and (col2 = ds.col2)) 
otherCondition=()
+----NestedLoopJoin[INNER_JOIN]
 ------PhysicalOneRowRelation
 ------filter((ds.col1 = 'abc') and (ds.col2 = 'def'))
 --------PhysicalOlapScan[test_pull_up_predicate_literal(ds)]
diff --git 
a/regression-test/suites/query_p0/cte/test_constant_cte_partition_prune.groovy 
b/regression-test/suites/query_p0/cte/test_constant_cte_partition_prune.groovy
new file mode 100644
index 00000000000..be08e756747
--- /dev/null
+++ 
b/regression-test/suites/query_p0/cte/test_constant_cte_partition_prune.groovy
@@ -0,0 +1,80 @@
+// 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_constant_cte_partition_prune") {
+    sql "DROP TABLE IF EXISTS constant_cte_prune_t"
+    sql """
+        CREATE TABLE constant_cte_prune_t (
+            dt DATE,
+            sn VARCHAR(50),
+            v DOUBLE
+        ) ENGINE=OLAP
+        DUPLICATE KEY(dt, sn)
+        PARTITION BY RANGE(dt)
+        (PARTITION p20260101 VALUES [("2026-01-01"), ("2026-01-02")),
+         PARTITION p20260726 VALUES [("2026-07-26"), ("2026-07-27")),
+         PARTITION p20260727 VALUES [("2026-07-27"), ("2026-07-28")),
+         PARTITION p20260728 VALUES [("2026-07-28"), ("2026-07-29")),
+         PARTITION p20260729 VALUES [("2026-07-29"), ("2026-07-30")),
+         PARTITION p20260901 VALUES [("2026-09-01"), ("2026-09-02")))
+        DISTRIBUTED BY HASH(sn) BUCKETS 3
+        PROPERTIES ("replication_num" = "1")
+    """
+
+    // force the constant CTE `params` to be inlined (it is referenced twice), 
matching the
+    // reported scenario; after inlining the constants must propagate out so 
that predicates
+    // over the CTE columns (including DATE_SUB() over them) are folded and 
the scans prune
+    // to a single day partition
+    sql "SET inline_cte_referenced_threshold=2"
+
+    // seed every partition so that PRUNE_EMPTY_PARTITION does not eliminate 
the scans
+    // (an all-empty table would rewrite the scan to an empty relation and the
+    // partition-pruning info below would not show up in the explain output)
+    sql """INSERT INTO constant_cte_prune_t VALUES
+        ('2026-01-01', 'sn1', 1.0),
+        ('2026-07-26', 'sn2', 2.0),
+        ('2026-07-27', 'sn3', 3.0),
+        ('2026-07-28', 'sn4', 4.0),
+        ('2026-07-29', 'sn5', 5.0),
+        ('2026-09-01', 'sn6', 6.0)"""
+
+    explain {
+        sql """
+            WITH params AS (
+                SELECT CAST('2026-07-28 00:00:00' AS DATETIME) AS begin_time,
+                       CAST('2026-07-28 23:59:59' AS DATETIME) AS end_time,
+                       DATEDIFF(CAST('2026-07-28 23:59:59' AS DATETIME),
+                                CAST('2026-07-28 00:00:00' AS DATETIME)) + 1 
AS period_days
+            ),
+            current_data AS (
+                SELECT SUM(v) AS total_value
+                FROM constant_cte_prune_t JOIN params ON 1=1
+                WHERE dt BETWEEN params.begin_time AND params.end_time
+            ),
+            last_period_data AS (
+                SELECT SUM(v) AS total_value
+                FROM constant_cte_prune_t JOIN params ON 1=1
+                WHERE dt BETWEEN DATE_SUB(params.begin_time, INTERVAL 
params.period_days DAY)
+                             AND DATE_SUB(params.begin_time, INTERVAL 1 DAY)
+            )
+            SELECT * FROM current_data, last_period_data
+        """
+        // current_data only needs 2026-07-28, last_period_data only needs 
2026-07-27
+        contains("partitions=1/6 (p20260728)")
+        contains("partitions=1/6 (p20260727)")
+    }
+}


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

Reply via email to