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 2ed8b169387 [fix](mv) Distinguish partition compensation from UNION 
ALL rewrite (#66445)
2ed8b169387 is described below

commit 2ed8b169387c28d6ba1b3f21f70d4d973adcc4a2
Author: foxtail463 <[email protected]>
AuthorDate: Wed Aug 12 17:27:36 2026 +0800

    [fix](mv) Distinguish partition compensation from UNION ALL rewrite (#66445)
    
    Problem Summary:
    MV rewrite treated removing invalid MV partitions as requiring UNION
    ALL, causing
    valid rewrites to be rejected.
    
    Solution:
    Separate partition compensation detection from base-table partition
    union requirements.
    Only check UNION ALL capability when base-table compensation is needed.
    
    ---------
    
    Co-authored-by: yangtao555 <[email protected]>
---
 .../mv/AbstractMaterializedViewRule.java           |  24 ++--
 ...terializedViewAggregateOnNoneAggregateRule.java |   4 +-
 .../rules/exploration/mv/PartitionCompensator.java |   7 -
 .../union_rewrite/partition_curd_union_rewrite.out |   7 +
 .../partition_curd_union_rewrite.groovy            | 143 +++++++++++++++++++++
 5 files changed, 162 insertions(+), 23 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java
index 38c4fade7ff..5b24abedbbb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AbstractMaterializedViewRule.java
@@ -375,17 +375,20 @@ public abstract class AbstractMaterializedViewRule 
implements ExplorationRuleFac
                     // if mv can not offer any partition for query, query 
rewrite bail out to avoid cycle run
                     return rewriteResults;
                 }
-                boolean partitionNeedUnion = 
PartitionCompensator.needUnionRewrite(invalidPartitions, cascadesContext);
-                boolean canUnionRewrite = canUnionRewrite(queryPlan,
-                        (AsyncMaterializationContext) materializationContext, 
cascadesContext);
-                if (partitionNeedUnion && !canUnionRewrite) {
+                boolean needRemoveMvPartitions = 
!invalidPartitions.key().isEmpty();
+                boolean needBaseTableUnion = 
!invalidPartitions.value().isEmpty();
+                if (needBaseTableUnion && !canUnionRewrite(queryPlan,
+                        (AsyncMaterializationContext) materializationContext, 
cascadesContext)) {
                     materializationContext.recordFailReason(queryStructInfo,
                             "need compensate union all, but can not, because 
the query structInfo",
                             () -> String.format("mv partition info is %s, and 
the query plan is %s",
                                     mtmv.getMvPartitionInfo(), 
queryPlan.treeString()));
                     return rewriteResults;
                 }
-                if (partitionNeedUnion) {
+                if (needRemoveMvPartitions) {
+                    rewrittenPlan = rewrittenPlan.accept(new 
PartitionRemover(), invalidPartitions.key());
+                }
+                if (needBaseTableUnion) {
                     Pair<Plan, Boolean> planAndNeedAddFilterPair =
                             StructInfo.addFilterOnTableScan(queryPlan, 
invalidPartitions.value(), cascadesContext);
                     if (planAndNeedAddFilterPair == null) {
@@ -395,15 +398,8 @@ public abstract class AbstractMaterializedViewRule 
implements ExplorationRuleFac
                                         invalidPartitions, 
queryPlan.treeString()));
                         continue;
                     }
-                    if (invalidPartitions.value().isEmpty() || 
!planAndNeedAddFilterPair.value()) {
-                        // if invalid base table filter is empty or doesn't 
need to add filter on base table,
-                        // only need remove mv invalid partition
-                        rewrittenPlan = rewrittenPlan.accept(new 
PartitionRemover(), invalidPartitions.key());
-                    } else {
-                        // For rewrittenPlan which contains materialized view 
should remove invalid partition ids
-                        List<Plan> children = Lists.newArrayList(
-                                rewrittenPlan.accept(new PartitionRemover(), 
invalidPartitions.key()),
-                                planAndNeedAddFilterPair.key());
+                    if (planAndNeedAddFilterPair.value()) {
+                        List<Plan> children = 
Lists.newArrayList(rewrittenPlan, planAndNeedAddFilterPair.key());
                         // Union query materialized view and source table
                         rewrittenPlan = new LogicalUnion(Qualifier.ALL,
                                 
queryPlan.getOutput().stream().map(NamedExpression.class::cast)
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewAggregateOnNoneAggregateRule.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewAggregateOnNoneAggregateRule.java
index 883b6651ae7..7155f661ee7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewAggregateOnNoneAggregateRule.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewAggregateOnNoneAggregateRule.java
@@ -105,8 +105,8 @@ public class MaterializedViewAggregateOnNoneAggregateRule 
extends AbstractMateri
         Pair<Map<BaseTableInfo, Set<String>>, Map<BaseColInfo, Set<String>>> 
invalidPartitions
                 = super.calcInvalidPartitions(queryUsedBaseTablePartitionMap, 
rewrittenPlan, cascadesContext,
                 materializationContext);
-        if (PartitionCompensator.needUnionRewrite(invalidPartitions, 
cascadesContext)) {
-            // if query use some invalid partition in mv, bail out
+        if (invalidPartitions != null && !invalidPartitions.value().isEmpty()) 
{
+            // Aggregate-on-non-aggregate rewrite does not support base-table 
union compensation.
             return null;
         }
         return invalidPartitions;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensator.java
index 9064049d3b8..7b07130aa59 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensator.java
@@ -220,13 +220,6 @@ public class PartitionCompensator {
         return Pair.of(mvPartitionNeedRemoveNameMap, 
baseTablePartitionNeedUnionNameMap);
     }
 
-    public static boolean needUnionRewrite(
-            Pair<Map<BaseTableInfo, Set<String>>, Map<BaseColInfo, 
Set<String>>> invalidPartitions,
-            CascadesContext cascadesContext) {
-        return invalidPartitions != null
-                && (!invalidPartitions.key().values().isEmpty() || 
!invalidPartitions.value().values().isEmpty());
-    }
-
     /**
      * Check if need union compensate or not
      * If query base table all partitions with ALL_PARTITIONS or 
ALL_PARTITIONS_LIST, should not do union compensate
diff --git 
a/regression-test/data/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.out
 
b/regression-test/data/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.out
new file mode 100644
index 00000000000..a3e98f21cb0
--- /dev/null
+++ 
b/regression-test/data/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.out
@@ -0,0 +1,7 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !aggregate_without_partition_column_after_partition_delete --
+1      20
+
+-- !aggregate_on_detail_mv_after_partition_delete --
+1      1
+
diff --git 
a/regression-test/suites/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.groovy
 
b/regression-test/suites/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.groovy
index 43c90c0df89..d5a1ac82872 100644
--- 
a/regression-test/suites/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.groovy
+++ 
b/regression-test/suites/nereids_rules_p0/mv/union_rewrite/partition_curd_union_rewrite.groovy
@@ -231,4 +231,147 @@ suite ("partition_curd_union_rewrite") {
     mv_rewrite_success(partition_sql, mv_name,
             is_partition_statistics_ready(db, ["lineitem", "orders", mv_name]))
     compare_res(partition_sql + order_by_stmt)
+
+    // Use exactly two base partitions. After p2 is dropped, p2 is stale only 
in the MV: no
+    // surviving base partition needs UNION ALL compensation. The query omits 
the MV partition column d,
+    // so canUnionRewrite() must be false while the rewrite still succeeds by 
removing stale MV p2.
+    sql "DROP MATERIALIZED VIEW IF EXISTS partition_compensation_mv"
+    sql "DROP TABLE IF EXISTS partition_compensation_mv"
+    sql "DROP TABLE IF EXISTS partition_compensation_base"
+    sql """
+    CREATE TABLE partition_compensation_base (
+        id int not null,
+        k int not null,
+        d date not null,
+        v int not null
+    )
+    DUPLICATE KEY(id, k, d)
+    PARTITION BY RANGE(d) (
+        PARTITION p1 VALUES [('2024-01-01'), ('2024-01-02')),
+        PARTITION p2 VALUES [('2024-01-02'), ('2024-01-03'))
+    )
+    DISTRIBUTED BY HASH(id) BUCKETS 1
+    PROPERTIES ('replication_num' = '1')
+    """
+    sql """
+    INSERT INTO partition_compensation_base VALUES
+        (1, 1, '2024-01-01', 1),
+        (2, 1, '2024-01-01', 1),
+        (3, 1, '2024-01-01', 1),
+        (4, 1, '2024-01-01', 1),
+        (5, 1, '2024-01-01', 1),
+        (6, 1, '2024-01-01', 1),
+        (7, 1, '2024-01-01', 1),
+        (8, 1, '2024-01-01', 1),
+        (9, 1, '2024-01-01', 1),
+        (10, 1, '2024-01-01', 1),
+        (11, 1, '2024-01-01', 1),
+        (12, 1, '2024-01-01', 1),
+        (13, 1, '2024-01-01', 1),
+        (14, 1, '2024-01-01', 1),
+        (15, 1, '2024-01-01', 1),
+        (16, 1, '2024-01-01', 1),
+        (17, 1, '2024-01-01', 1),
+        (18, 1, '2024-01-01', 1),
+        (19, 1, '2024-01-01', 1),
+        (20, 1, '2024-01-01', 1),
+        (21, 1, '2024-01-02', 1)
+    """
+
+    def partition_compensation_mv_name = "partition_compensation_mv"
+    def aggregate_without_partition_column_sql = """
+    select k, sum(v) as sum_v
+    from partition_compensation_base
+    group by k
+    """
+    sql """
+    CREATE MATERIALIZED VIEW ${partition_compensation_mv_name}
+    BUILD IMMEDIATE REFRESH AUTO ON MANUAL
+    PARTITION BY(d)
+    DISTRIBUTED BY RANDOM BUCKETS 1
+    PROPERTIES ('replication_num' = '1')
+    AS
+    select d, k, sum(v) as sum_v
+    from partition_compensation_base
+    group by d, k
+    """
+    waitingMTMVTaskFinished(getJobName(db, partition_compensation_mv_name))
+    sql """
+    analyze table partition_compensation_base with sync;
+    analyze table ${partition_compensation_mv_name} with sync;
+    """
+
+    sql "ALTER TABLE partition_compensation_base DROP PARTITION p2 FORCE"
+    waitingPartitionIsExpected(partition_compensation_mv_name, 
"p_20240102_20240103", false)
+    mv_rewrite_success(aggregate_without_partition_column_sql, 
partition_compensation_mv_name,
+            is_partition_statistics_ready(db, ["partition_compensation_base", 
partition_compensation_mv_name]))
+    explain {
+        sql "memo plan ${aggregate_without_partition_column_sql}"
+        notContains "PhysicalUnion"
+    }
+    order_qt_aggregate_without_partition_column_after_partition_delete """
+    ${aggregate_without_partition_column_sql}
+    order by k
+    """
+
+    // Aggregate-on-detail rewrite has the same removal-only partition state. 
It should remove the stale
+    // detail MV partition and must not require a base-table UNION ALL branch.
+    sql "DROP MATERIALIZED VIEW IF EXISTS partition_compensation_detail_mv"
+    sql "DROP TABLE IF EXISTS partition_compensation_detail_mv"
+    sql "DROP TABLE IF EXISTS partition_compensation_detail_base"
+    sql """
+    CREATE TABLE partition_compensation_detail_base (
+        id int not null,
+        k int not null,
+        d date not null,
+        v int not null
+    )
+    DUPLICATE KEY(id, k, d)
+    PARTITION BY RANGE(d) (
+        PARTITION p1 VALUES [('2024-02-01'), ('2024-02-02')),
+        PARTITION p2 VALUES [('2024-02-02'), ('2024-02-03'))
+    )
+    DISTRIBUTED BY HASH(id) BUCKETS 1
+    PROPERTIES ('replication_num' = '1')
+    """
+    sql """
+    INSERT INTO partition_compensation_detail_base VALUES
+        (1, 1, '2024-02-01', 1),
+        (2, 1, '2024-02-02', 1)
+    """
+
+    def aggregate_on_detail_mv_sql = """
+    select k, sum(v) as sum_v
+    from partition_compensation_detail_base
+    group by k
+    """
+    sql """
+    CREATE MATERIALIZED VIEW partition_compensation_detail_mv
+    BUILD IMMEDIATE REFRESH AUTO ON MANUAL
+    PARTITION BY(d)
+    DISTRIBUTED BY RANDOM BUCKETS 1
+    PROPERTIES ('replication_num' = '1')
+    AS
+    select id, k, d, v
+    from partition_compensation_detail_base
+    """
+    waitingMTMVTaskFinished(getJobName(db, "partition_compensation_detail_mv"))
+    sql """
+    analyze table partition_compensation_detail_base with sync;
+    analyze table partition_compensation_detail_mv with sync;
+    """
+
+    sql "ALTER TABLE partition_compensation_detail_base DROP PARTITION p2 
FORCE"
+    waitingPartitionIsExpected("partition_compensation_detail_mv", 
"p_20240202_20240203", false)
+    mv_rewrite_success(aggregate_on_detail_mv_sql, 
"partition_compensation_detail_mv",
+            is_partition_statistics_ready(
+                    db, ["partition_compensation_detail_base", 
"partition_compensation_detail_mv"]))
+    explain {
+        sql "memo plan ${aggregate_on_detail_mv_sql}"
+        notContains "PhysicalUnion"
+    }
+    order_qt_aggregate_on_detail_mv_after_partition_delete """
+    ${aggregate_on_detail_mv_sql}
+    order by k
+    """
 }


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

Reply via email to