This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new f90ad3757db branch-4.2: [fix](mtmv) Keep constant-propagated
predicates in materialized view rewrite #69464 (#69539)
f90ad3757db is described below
commit f90ad3757db5686f4bea41fac1a2f1536259bfd8
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Oct 24 20:51:29 2026 +0800
branch-4.2: [fix](mtmv) Keep constant-propagated predicates in materialized
view rewrite #69464 (#69539)
Cherry-picked from #69464
Co-authored-by: yujun <[email protected]>
---
.../plans/visitor/ExpressionLineageReplacer.java | 25 +++++
.../doris/nereids/util/ExpressionUtilsTest.java | 23 ++++
.../mv_rewrite_constant_group_key_predicate.out | 9 ++
.../mv_rewrite_constant_group_key_predicate.groovy | 121 +++++++++++++++++++++
4 files changed, 178 insertions(+)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java
index 346e748f390..c88543c9767 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/ExpressionLineageReplacer.java
@@ -59,12 +59,37 @@ public class ExpressionLineageReplacer extends
DefaultPlanVisitor<Expression, Ex
if (!usedExprIdSet.contains(((NamedExpression)
expression).getExprId())) {
continue;
}
+ if (isConstantShadowingLowerColumn(plan, (NamedExpression)
expression)) {
+ continue;
+ }
// Get used named expression by used expr id set
expression.accept(NamedExpressionCollector.INSTANCE, context);
}
return super.visit(plan, context);
}
+ /**
+ * Constant propagation materializes a constant above an aggregate, e.g.
{@code 2026 AS dt_year#4},
+ * and reuses the exprId of the replaced output. When the same exprId is
still defined by the plan
+ * below, the constant is not the lineage of the expressions which are
evaluated there, so it must not
+ * replace them. Otherwise a predicate under the aggregate, such as {@code
dt_year#4 = 2026}, would be
+ * rewritten to {@code 2026 = 2026} and removed, and a materialized view
rewrite compensating that
+ * predicate would read the unfiltered rows.
+ */
+ private static boolean isConstantShadowingLowerColumn(Plan plan,
NamedExpression namedExpression) {
+ if (!(namedExpression instanceof Alias) ||
!namedExpression.child(0).isConstant()) {
+ return false;
+ }
+ ExprId exprId = namedExpression.getExprId();
+ for (Plan child : plan.children()) {
+ if (child.anyMatch(node -> node instanceof Plan
+ && ((Plan) node).getOutput().stream().anyMatch(slot ->
slot.getExprId().equals(exprId)))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public Expression visitGroupPlan(GroupPlan groupPlan,
ExpressionReplaceContext context) {
if (LOG.isDebugEnabled()) {
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java
index a0016dfdf5d..0fc2a93cba3 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/ExpressionUtilsTest.java
@@ -26,6 +26,7 @@ import org.apache.doris.nereids.trees.expressions.EqualTo;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
import org.apache.doris.nereids.trees.expressions.functions.generator.Explode;
import
org.apache.doris.nereids.trees.expressions.functions.generator.ExplodeBitmap;
import
org.apache.doris.nereids.trees.expressions.functions.generator.ExplodeBitmapOuter;
@@ -44,6 +45,7 @@ import
org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.MapLiteral;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalOdbcScan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.types.IntegerType;
@@ -245,6 +247,27 @@ public class ExpressionUtilsTest extends TestWithFeService
{
Assertions.assertEquals(expressions, ImmutableList.of(a,
bAlias.toSlot()));
}
+ @Test
+ public void testShuttleDoesNotUseConstantShadowingLowerColumn() {
+ OlapTable olapTable = TableTest.newOlapTable(10000, "test", 0);
+ Slot a = new SlotReference("a", IntegerType.INSTANCE);
+ Slot b = new SlotReference("b", IntegerType.INSTANCE);
+ LogicalProject<LogicalOdbcScan> child = new
LogicalProject<>(ImmutableList.of(a, b),
+ new LogicalOdbcScan(new RelationId(0), olapTable,
ImmutableList.of("test")));
+ // the aggregate removes `a` from its output, like constant
propagation on a constant grouping key
+ LogicalAggregate<LogicalProject<LogicalOdbcScan>> aggregate = new
LogicalAggregate<>(
+ ImmutableList.of(b), ImmutableList.of(b, new Alias(new Sum(a),
"sum_a")), child);
+ // constant propagation materializes the removed grouping key above
the aggregate and reuses its exprId
+ LogicalProject<LogicalAggregate<LogicalProject<LogicalOdbcScan>>>
project = new LogicalProject<>(
+ ImmutableList.of(new Alias(a.getExprId(), new
IntegerLiteral(1), "a"), b), aggregate);
+ Expression predicate = new EqualTo(a, new IntegerLiteral(1));
+ List<? extends Expression> shuttledExpressions
+ =
ExpressionUtils.shuttleExpressionWithLineage(ImmutableList.of(predicate),
project);
+ // `a` is still defined below the project, so the constant above must
not replace it in the predicate,
+ // otherwise the predicate becomes `1 = 1` and is dropped
+ Assertions.assertEquals(ImmutableList.of(predicate),
shuttledExpressions);
+ }
+
@Test
public void testReplaceNullAware() {
Slot a = new SlotReference("id1", IntegerType.INSTANCE);
diff --git
a/regression-test/data/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.out
b/regression-test/data/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.out
new file mode 100644
index 00000000000..3ca981e0e93
--- /dev/null
+++
b/regression-test/data/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.out
@@ -0,0 +1,9 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !query --
+2026 G0 10.00
+2026 G1 20.00
+
+-- !query_without_mv --
+2026 G0 10.00
+2026 G1 20.00
+
diff --git
a/regression-test/suites/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.groovy
b/regression-test/suites/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.groovy
new file mode 100644
index 00000000000..9770cbaf0be
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/mv/constant_group_key_predicate/mv_rewrite_constant_group_key_predicate.groovy
@@ -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.
+
+/*
+ * Constant propagation materializes a constant grouping key above the
aggregate,
+ * e.g. `2026 AS dt_year#4`, and reuses the exprId of the column. The
predicate which
+ * filters that column below the aggregate must still be compensated when the
query is
+ * rewritten by the materialized view, otherwise the rewritten plan aggregates
the rows
+ * of every year and returns a wrong result.
+ */
+suite("mv_rewrite_constant_group_key_predicate") {
+ String db = context.config.getDbNameByFile(context.file)
+ sql "use ${db}"
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+ sql "SET enable_sql_cache=false"
+ sql "SET enable_materialized_view_rewrite=true"
+ sql "SET pre_materialized_view_rewrite_strategy=NOT_IN_RBO"
+
+ sql "drop materialized view if exists mv_constant_group_key"
+ sql "drop table if exists fact_constant_group_key"
+ sql "drop table if exists dim_constant_group_key_date"
+ sql "drop table if exists dim_constant_group_key_account"
+
+ sql """
+ create table fact_constant_group_key (
+ data_date date not null,
+ account_id int not null,
+ amount decimal(20, 2)
+ ) engine=olap
+ duplicate key(data_date, account_id)
+ partition by range(data_date) (from ('2026-01-01') to ('2026-02-01')
interval 1 day)
+ distributed by hash(account_id) buckets 1
+ properties("replication_num" = "1")
+ """
+
+ sql """
+ create table dim_constant_group_key_date (
+ data_date date not null,
+ dt_year int,
+ dt_quarter int
+ ) engine=olap
+ duplicate key(data_date)
+ distributed by hash(data_date) buckets 1
+ properties("replication_num" = "1")
+ """
+
+ sql """
+ create table dim_constant_group_key_account (
+ account_id int not null,
+ org_group varchar(20),
+ account_type varchar(20)
+ ) engine=olap
+ duplicate key(account_id)
+ distributed by hash(account_id) buckets 1
+ properties("replication_num" = "1")
+ """
+
+ // the same fact day carries the rows of two different years, so reading
every partition
+ // changes the aggregation result even though only one day is written
+ sql """insert into dim_constant_group_key_date values ('2026-01-05', 2025,
1), ('2026-01-06', 2026, 1)"""
+ sql """insert into dim_constant_group_key_account values (1, 'G0',
'NORMAL'), (2, 'G1', 'NORMAL')"""
+ sql """insert into fact_constant_group_key values
+ ('2026-01-05', 1, 100), ('2026-01-06', 1, 10), ('2026-01-06', 2, 20)"""
+
+ sql """
+ create materialized view mv_constant_group_key
+ build immediate refresh complete on schedule every 1 hour
+ partition by (data_date)
+ distributed by hash(org_group) buckets 1
+ properties("replication_num" = "1")
+ as
+ select f.data_date, d.dt_year, d.dt_quarter, a.org_group,
a.account_type, sum(f.amount) as amount
+ from fact_constant_group_key f
+ left join dim_constant_group_key_date d on d.data_date = f.data_date
+ left join dim_constant_group_key_account a on a.account_id =
f.account_id
+ group by f.data_date, d.dt_year, d.dt_quarter, a.org_group,
a.account_type
+ """
+ waitingMTMVTaskFinishedByMvName("mv_constant_group_key")
+
+ def query = """
+ select dt_year, org_group, sum(amount)
+ from (
+ select f.amount, d.dt_year, d.dt_quarter, a.org_group,
a.account_type
+ from fact_constant_group_key f
+ left join dim_constant_group_key_date d on d.data_date =
f.data_date
+ left join dim_constant_group_key_account a on a.account_id =
f.account_id
+ ) q
+ where dt_year in (2026) and org_group in ('G0', 'G1')
+ and dt_quarter <= 3 and account_type <> 'X'
+ group by dt_year, org_group
+ """
+
+ def explain = sql("explain ${query}").collect { it[0] }.join("\n")
+ def choseStart = explain.indexOf("MaterializedViewRewriteSuccessAndChose:")
+ def choseEnd =
explain.indexOf("MaterializedViewRewriteSuccessButNotChose:")
+ assertTrue(choseStart != -1 && choseEnd > choseStart, explain)
+ assertTrue(explain.substring(choseStart,
choseEnd).contains("mv_constant_group_key"),
+ "the query should be rewritten by
mv_constant_group_key:\n${explain}")
+
+ order_qt_query "${query}"
+
+ // the same query without the materialized view is the reference result
+ sql "SET enable_materialized_view_rewrite=false"
+ order_qt_query_without_mv "${query}"
+ sql "SET enable_materialized_view_rewrite=true"
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]