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]