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 7cea7c9c216  [opt](mtmv) Optimize mv rewrite performance when mv has 
many partition by reduce partition mapping num (#59972)
7cea7c9c216 is described below

commit 7cea7c9c216554b77e3e4bcecb1386d1d6bc6f0e
Author: seawinde <[email protected]>
AuthorDate: Thu Aug 13 16:21:32 2026 +0800

     [opt](mtmv) Optimize mv rewrite performance when mv has many partition by 
reduce partition mapping num (#59972)
    
    Summary
    
    - Make MTMV partition descriptor generation honor queryUsedPartitions,
    so only partitions referenced by the current query are carried through
    the pipeline instead of all partitions.
    - Applied the filter in
    MTMVRelatedPartitionDescOnePartitionColGenerator, preserving ordering of
    existing generators.
    - Added unit tests for RANGE and LIST sources to assert the filter
    reduces descriptors to the specified partitions.
    
    Example
    If a query only touches t1 partition p20210201,
    generateRelatedPartitionDescs now emits a single descriptor for
    p20210201 instead of all t1 partitions;
---
 .../main/java/org/apache/doris/catalog/MTMV.java   |  39 ++-
 .../apache/doris/job/extensions/mtmv/MTMVTask.java |   2 +-
 .../apache/doris/mtmv/MTMVPartitionExpander.java   | 124 ++++++++++
 .../org/apache/doris/mtmv/MTMVPartitionUtil.java   |  46 +++-
 .../org/apache/doris/mtmv/MTMVRefreshContext.java  |   8 +-
 .../MTMVRelatedPartitionDescGeneratorService.java  |   4 +-
 .../MTMVRelatedPartitionDescInitGenerator.java     |   4 +-
 ...latedPartitionDescOnePartitionColGenerator.java |   8 +-
 .../MTMVRelatedPartitionDescRollUpGenerator.java   |   3 +-
 ...MTMVRelatedPartitionDescSyncLimitGenerator.java |   4 +-
 .../MTMVRelatedPartitionDescTransferGenerator.java |   3 +-
 .../org/apache/doris/mtmv/MTMVRewriteUtil.java     |   3 +-
 .../mv/AsyncMaterializationContext.java            |  69 +++++-
 .../rules/exploration/mv/PartitionCompensator.java |   2 +-
 .../apache/doris/mtmv/MTMVExpandPartitionTest.java | 269 +++++++++++++++++++++
 .../apache/doris/mtmv/MTMVPartitionUtilTest.java   |  78 +++++-
 .../MTMVRelatedPartitionDescGeneratorTest.java     |  55 ++++-
 .../mv/AsyncMaterializationContextTest.java        | 136 +++++++++++
 .../exploration/mv/PartitionCompensatorTest.java   |   4 +-
 19 files changed, 818 insertions(+), 43 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java
index c760d63563e..d0fa55678d7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/MTMV.java
@@ -31,6 +31,7 @@ import org.apache.doris.mtmv.EnvInfo;
 import org.apache.doris.mtmv.MTMVCache;
 import org.apache.doris.mtmv.MTMVJobInfo;
 import org.apache.doris.mtmv.MTMVJobManager;
+import org.apache.doris.mtmv.MTMVPartitionExpander;
 import org.apache.doris.mtmv.MTMVPartitionInfo;
 import org.apache.doris.mtmv.MTMVPartitionInfo.MTMVPartitionType;
 import org.apache.doris.mtmv.MTMVPartitionUtil;
@@ -56,6 +57,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
@@ -522,6 +524,27 @@ public class MTMV extends OlapTable {
         return result;
     }
 
+    /**
+     * Normalize query-used base table partitions to the effective filter 
consumed by
+     * partition-mapping generation.
+     */
+    public Map<List<String>, Set<String>> 
getEffectiveQueryUsedBaseTablePartitionMap(
+            Map<List<String>, Set<String>> queryUsedBaseTablePartitionMap) 
throws AnalysisException {
+        return 
getEffectiveQueryUsedBaseTablePartitionMap(queryUsedBaseTablePartitionMap, 
null);
+    }
+
+    private Map<List<String>, Set<String>> 
getEffectiveQueryUsedBaseTablePartitionMap(
+            Map<List<String>, Set<String>> queryUsedBaseTablePartitionMap,
+            Map<String, PartitionItem> mvPartitionItems) throws 
AnalysisException {
+        if (queryUsedBaseTablePartitionMap.isEmpty()
+                || mvPartitionInfo.getPartitionType() != 
MTMVPartitionType.EXPR) {
+            return queryUsedBaseTablePartitionMap;
+        }
+        return 
MTMVPartitionExpander.expandToMvPartitionGranularity(queryUsedBaseTablePartitionMap,
+                mvPartitionItems != null ? mvPartitionItems : 
getAndCopyPartitionItems(),
+                mvPartitionInfo.getPctTables());
+    }
+
     /**
      * Calculate the partition and associated partition mapping relationship 
of the MTMV
      * It is the result of real-time comparison calculation, so there may be 
some costs,
@@ -530,15 +553,25 @@ public class MTMV extends OlapTable {
      * @return mvPartitionName ==> pctTable ==> pctPartitionName
      * @throws AnalysisException
      */
-    public Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
calculatePartitionMappings() throws AnalysisException {
+    public Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
calculatePartitionMappings(
+            Map<List<String>, Set<String>> queryUsedBaseTablePartitionMap) 
throws AnalysisException {
         if (mvPartitionInfo.getPartitionType() == 
MTMVPartitionType.SELF_MANAGE) {
             return Maps.newHashMap();
         }
         long start = System.currentTimeMillis();
+        // For EXPR-type partitions with RANGE base tables, expand the 
query-used partition
+        // filter to MV partition granularity. This ensures complete partition 
mappings per
+        // MV partition (needed for isSyncWithPartitions correctness) while 
skipping
+        // irrelevant MV partitions entirely (the performance optimization).
+        // For nested MVs where pctTable is not in the filter, the expanded 
map is empty,
+        // so the pipeline runs without filtering (full computation) — correct 
behavior.
+        Map<String, PartitionItem> mvPartitionItems = 
getAndCopyPartitionItems();
+        Map<List<String>, Set<String>> effectiveFilter
+                = 
getEffectiveQueryUsedBaseTablePartitionMap(queryUsedBaseTablePartitionMap, 
mvPartitionItems);
         Map<String, Map<MTMVRelatedTableIf, Set<String>>> res = 
Maps.newHashMap();
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
pctPartitionDescs = MTMVPartitionUtil
-                .generateRelatedPartitionDescs(mvPartitionInfo, mvProperties, 
getPartitionColumns());
-        Map<String, PartitionItem> mvPartitionItems = 
getAndCopyPartitionItems();
+                .generateRelatedPartitionDescs(mvPartitionInfo, mvProperties, 
getPartitionColumns(),
+                        effectiveFilter);
         for (Entry<String, PartitionItem> entry : mvPartitionItems.entrySet()) 
{
             res.put(entry.getKey(),
                     
pctPartitionDescs.getOrDefault(entry.getValue().toPartitionKeyDesc(), 
Maps.newHashMap()));
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java 
b/fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java
index 9766a175a56..95e79495c45 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java
@@ -240,7 +240,7 @@ public class MTMVTask extends AbstractTask {
             }
             MetaLockUtils.readLockTables(tableIfs);
             try {
-                context = MTMVRefreshContext.buildContext(mtmv);
+                context = MTMVRefreshContext.buildContext(mtmv, 
Maps.newHashMap());
                 this.needRefreshPartitions = 
calculateNeedRefreshPartitions(context);
             } finally {
                 MetaLockUtils.readUnlockTables(tableIfs);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExpander.java 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExpander.java
new file mode 100644
index 00000000000..7a20b9fd0a0
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExpander.java
@@ -0,0 +1,124 @@
+// 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.mtmv;
+
+import org.apache.doris.catalog.PartitionItem;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PartitionType;
+import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.datasource.mvcc.MvccSnapshot;
+import org.apache.doris.datasource.mvcc.MvccUtil;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Range;
+import com.google.common.collect.Sets;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Utility to expand query-used partition filters to MV partition granularity
+ * using Range.encloses(), avoiding expensive dateTrunc / strToDate / 
dateIncrement
+ * per-partition operations in the rollup pipeline.
+ * Separated from MTMV to keep a lightweight dependency tree for testability —
+ * loading this class does not trigger MTMV → OlapTable → CloudReplica class 
loading.
+ */
+public class MTMVPartitionExpander {
+
+    /**
+     * Expand queryUsedPartitions to MV partition granularity for RANGE base 
tables.
+     * For example, if MV is monthly partitioned (date_trunc(month)) and base 
table is daily:
+     * - Query uses p_20250115 (Jan 15)
+     * - Find MV partition p_202501 that encloses [20250115, 20250116)
+     * - Expand to ALL daily partitions within p_202501's range [20250101, 
20250201)
+     * - Result: {p_20250101, p_20250102, ..., p_20250131}
+     */
+    public static Map<List<String>, Set<String>> 
expandToMvPartitionGranularity(
+            Map<List<String>, Set<String>> queryUsedBaseTablePartitionMap,
+            Map<String, PartitionItem> mvPartitionItems,
+            Set<MTMVRelatedTableIf> pctTables) throws AnalysisException {
+        NavigableMap<PartitionKey, Range<PartitionKey>> mvRanges = new 
TreeMap<>();
+        for (PartitionItem item : mvPartitionItems.values()) {
+            Range<PartitionKey> range = ((RangePartitionItem) item).getItems();
+            mvRanges.put(range.lowerEndpoint(), range);
+        }
+
+        Map<List<String>, Set<String>> expanded = Maps.newHashMap();
+        for (MTMVRelatedTableIf pctTable : pctTables) {
+            List<String> qualifiers = pctTable.getFullQualifiers();
+            Set<String> queryUsedPartitions = 
queryUsedBaseTablePartitionMap.get(qualifiers);
+            if (queryUsedPartitions == null) {
+                continue;
+            }
+
+            Optional<MvccSnapshot> snapshot = 
MvccUtil.getSnapshotFromContext(pctTable);
+            if (pctTable.getPartitionType(snapshot) != PartitionType.RANGE) {
+                expanded.put(qualifiers, queryUsedPartitions);
+                continue;
+            }
+
+            Map<String, PartitionItem> basePartitionItems = 
pctTable.getAndCopyPartitionItems(snapshot);
+
+            NavigableMap<PartitionKey, Range<PartitionKey>> relevantMvRanges = 
new TreeMap<>();
+            for (String queriedBasePartition : queryUsedPartitions) {
+                PartitionItem baseItem = 
basePartitionItems.get(queriedBasePartition);
+                if (baseItem == null) {
+                    continue;
+                }
+                Range<PartitionKey> baseRange = ((RangePartitionItem) 
baseItem).getItems();
+                Range<PartitionKey> mvRange = findEnclosingRange(mvRanges, 
baseRange);
+                if (mvRange != null) {
+                    relevantMvRanges.put(mvRange.lowerEndpoint(), mvRange);
+                }
+            }
+
+            if (relevantMvRanges.isEmpty()) {
+                expanded.put(qualifiers, Sets.newHashSet());
+                continue;
+            }
+
+            Set<String> expandedPartitions = Sets.newHashSet();
+            for (Entry<String, PartitionItem> baseEntry : 
basePartitionItems.entrySet()) {
+                Range<PartitionKey> baseRange = ((RangePartitionItem) 
baseEntry.getValue()).getItems();
+                if (findEnclosingRange(relevantMvRanges, baseRange) != null) {
+                    expandedPartitions.add(baseEntry.getKey());
+                }
+            }
+
+            expanded.put(qualifiers, expandedPartitions);
+        }
+
+        return expanded;
+    }
+
+    private static Range<PartitionKey> findEnclosingRange(
+            NavigableMap<PartitionKey, Range<PartitionKey>> ranges, 
Range<PartitionKey> baseRange) {
+        // RANGE partitions do not overlap, so only the range with the nearest 
lower endpoint can enclose baseRange.
+        Entry<PartitionKey, Range<PartitionKey>> candidate = 
ranges.floorEntry(baseRange.lowerEndpoint());
+        return candidate != null && candidate.getValue().encloses(baseRange) ? 
candidate.getValue() : null;
+    }
+
+    private MTMVPartitionExpander() {
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionUtil.java
index d2bcfc4ca69..700ab47fa44 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionUtil.java
@@ -52,6 +52,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -136,7 +137,7 @@ public class MTMVPartitionUtil {
     public static Pair<List<String>, List<PartitionKeyDesc>> 
alignMvPartition(MTMV mtmv) throws AnalysisException {
         Map<String, PartitionKeyDesc> mtmvPartitionDescs = 
mtmv.generateMvPartitionDescs();
         Set<PartitionKeyDesc> relatedPartitionDescs = 
generateRelatedPartitionDescs(mtmv.getMvPartitionInfo(),
-                mtmv.getMvProperties(), mtmv.getPartitionColumns()).keySet();
+                mtmv.getMvProperties(), mtmv.getPartitionColumns(), 
Maps.newHashMap()).keySet();
         List<String> partitionsToDrop = new ArrayList<>();
         List<PartitionKeyDesc> partitionsToAdd = new ArrayList<>();
         // drop partition of mtmv
@@ -171,7 +172,7 @@ public class MTMVPartitionUtil {
         List<AllPartitionDesc> res = Lists.newArrayList();
         HashMap<String, String> partitionProperties = Maps.newHashMap();
         Set<PartitionKeyDesc> relatedPartitionDescs = 
generateRelatedPartitionDescs(mvPartitionInfo, mvProperties,
-                partitionColumns)
+                partitionColumns, Maps.newHashMap())
                 .keySet();
         for (PartitionKeyDesc partitionKeyDesc : relatedPartitionDescs) {
             SinglePartitionDesc singlePartitionDesc = new 
SinglePartitionDesc(true,
@@ -184,13 +185,24 @@ public class MTMVPartitionUtil {
         return res;
     }
 
+    /**
+     * generateRelatedPartitionDescs
+     *
+     * @param mvPartitionInfo materialized view mvPartitionInfo
+     * @param mvProperties materialized view mvProperties when created
+     * @param partitionColumns materialized view partition columns
+     * @param queryUsedPartitions partitions current query used
+     * @return map of mv related table partition descs
+     * @throws AnalysisException
+     */
     public static Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
generateRelatedPartitionDescs(
             MTMVPartitionInfo mvPartitionInfo,
-            Map<String, String> mvProperties, List<Column> partitionColumns) 
throws AnalysisException {
+            Map<String, String> mvProperties, List<Column> partitionColumns,
+            Map<List<String>, Set<String>> queryUsedPartitions) throws 
AnalysisException {
         long start = System.currentTimeMillis();
         RelatedPartitionDescResult result = new RelatedPartitionDescResult();
         for (MTMVRelatedPartitionDescGeneratorService service : 
partitionDescGenerators) {
-            service.apply(mvPartitionInfo, mvProperties, result, 
partitionColumns);
+            service.apply(mvPartitionInfo, mvProperties, result, 
partitionColumns, queryUsedPartitions);
         }
         if (LOG.isDebugEnabled()) {
             LOG.debug("generateRelatedPartitionDescs use [{}] mills, 
mvPartitionInfo is [{}]",
@@ -211,8 +223,8 @@ public class MTMVPartitionUtil {
             return false;
         }
         try {
-            return isMTMVSync(MTMVRefreshContext.buildContext(mtmv), 
mtmvRelation.getBaseTablesOneLevelAndFromView(),
-                    Sets.newHashSet());
+            return isMTMVSync(MTMVRefreshContext.buildContext(mtmv, 
Maps.newHashMap()),
+                    mtmvRelation.getBaseTablesOneLevelAndFromView(), 
Sets.newHashSet());
         } catch (AnalysisException e) {
             LOG.warn("isMTMVSync failed: ", e);
             return false;
@@ -253,7 +265,7 @@ public class MTMVPartitionUtil {
             throws AnalysisException {
         List<Long> partitionIds = mtmv.getPartitionIds();
         Map<Long, List<String>> res = Maps.newHashMap();
-        MTMVRefreshContext context = MTMVRefreshContext.buildContext(mtmv);
+        MTMVRefreshContext context = MTMVRefreshContext.buildContext(mtmv, 
Maps.newHashMap());
         for (Long partitionId : partitionIds) {
             String partitionName = 
mtmv.getPartitionOrAnalysisException(partitionId).getName();
             res.put(partitionId, getPartitionUnSyncTables(context, 
partitionName));
@@ -632,22 +644,34 @@ public class MTMVPartitionUtil {
         throw new AnalysisException("can not getPartitionColumnType by:" + 
col);
     }
 
-    public static MTMVBaseVersions getBaseVersions(MTMV mtmv) throws 
AnalysisException {
-        return new MTMVBaseVersions(getTableVersions(mtmv), 
getPartitionVersions(mtmv));
+    public static MTMVBaseVersions getBaseVersions(MTMV mtmv,
+            Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
partitionMappings) throws AnalysisException {
+        return new MTMVBaseVersions(getTableVersions(mtmv), 
getPartitionVersions(mtmv, partitionMappings));
     }
 
-    private static Map<MTMVRelatedTableIf, Map<String, Long>> 
getPartitionVersions(MTMV mtmv) throws AnalysisException {
+    private static Map<MTMVRelatedTableIf, Map<String, Long>> 
getPartitionVersions(MTMV mtmv,
+            Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
partitionMappings) throws AnalysisException {
         Map<MTMVRelatedTableIf, Map<String, Long>> res = Maps.newHashMap();
         if 
(mtmv.getMvPartitionInfo().getPartitionType().equals(MTMVPartitionType.SELF_MANAGE))
 {
             return res;
         }
+        Map<MTMVRelatedTableIf, Set<String>> mappedPartitionNames = 
Maps.newHashMap();
+        for (Map<MTMVRelatedTableIf, Set<String>> mapping : 
partitionMappings.values()) {
+            for (Entry<MTMVRelatedTableIf, Set<String>> entry : 
mapping.entrySet()) {
+                mappedPartitionNames.computeIfAbsent(entry.getKey(), key -> 
Sets.newHashSet())
+                        .addAll(entry.getValue());
+            }
+        }
         Set<MTMVRelatedTableIf> pctTables = 
mtmv.getMvPartitionInfo().getPctTables();
         for (MTMVRelatedTableIf pctTable : pctTables) {
             if (!(pctTable instanceof OlapTable)) {
                 continue;
             }
             Map<String, Long> onePctResult = Maps.newHashMap();
-            List<Partition> partitions = Lists.newArrayList(((OlapTable) 
pctTable).getPartitions());
+            List<Partition> partitions = Lists.newArrayList();
+            for (String partitionName : 
mappedPartitionNames.getOrDefault(pctTable, Collections.emptySet())) {
+                partitions.add(((OlapTable) 
pctTable).getPartitionOrAnalysisException(partitionName));
+            }
             List<Long> versions = null;
             try {
                 versions = Partition.getVisibleVersions(partitions);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRefreshContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRefreshContext.java
index 603f89bbec5..683d2f353c4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRefreshContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRefreshContext.java
@@ -22,6 +22,7 @@ import org.apache.doris.common.AnalysisException;
 
 import com.google.common.collect.Maps;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -58,10 +59,11 @@ public class MTMVRefreshContext {
         return baseTableSnapshotCache;
     }
 
-    public static MTMVRefreshContext buildContext(MTMV mtmv) throws 
AnalysisException {
+    public static MTMVRefreshContext buildContext(MTMV mtmv, Map<List<String>, 
Set<String>> queryUsedPartitions)
+            throws AnalysisException {
         MTMVRefreshContext context = new MTMVRefreshContext(mtmv);
-        context.partitionMappings = mtmv.calculatePartitionMappings();
-        context.baseVersions = MTMVPartitionUtil.getBaseVersions(mtmv);
+        context.partitionMappings = 
mtmv.calculatePartitionMappings(queryUsedPartitions);
+        context.baseVersions = MTMVPartitionUtil.getBaseVersions(mtmv, 
context.partitionMappings);
         return context;
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorService.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorService.java
index 5760995ac64..519cb54c20d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorService.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorService.java
@@ -22,6 +22,7 @@ import org.apache.doris.common.AnalysisException;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Interface for a series of processes to generate PartitionDesc
@@ -37,5 +38,6 @@ public interface MTMVRelatedPartitionDescGeneratorService {
      * @throws AnalysisException
      */
     void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException;
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+               Map<List<String>, Set<String>> queryUsedPartitionMap) throws 
AnalysisException;
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescInitGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescInitGenerator.java
index 82d45f55288..5d5b161ed5f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescInitGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescInitGenerator.java
@@ -35,8 +35,10 @@ public class MTMVRelatedPartitionDescInitGenerator 
implements MTMVRelatedPartiti
 
     @Override
     public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException {
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+                      Map<List<String>, Set<String>> queryUsedPartitionMap) 
throws AnalysisException {
         Set<MTMVRelatedTableIf> relatedTables = mvPartitionInfo.getPctTables();
+        // the key is related table, the value is partition items of the 
related table
         Map<MTMVRelatedTableIf, Map<String, PartitionItem>> items = 
Maps.newHashMap();
         for (MTMVRelatedTableIf relatedTable : relatedTables) {
             items.put(relatedTable,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescOnePartitionColGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescOnePartitionColGenerator.java
index 22148d0090f..21b3a21f346 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescOnePartitionColGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescOnePartitionColGenerator.java
@@ -49,16 +49,22 @@ public class 
MTMVRelatedPartitionDescOnePartitionColGenerator implements MTMVRel
 
     @Override
     public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException {
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+                      Map<List<String>, Set<String>> queryUsedPartitionMap) 
throws AnalysisException {
         if (mvPartitionInfo.getPartitionType() == 
MTMVPartitionType.SELF_MANAGE) {
             return;
         }
         Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> res = 
Maps.newHashMap();
+        // the key is mv related table, the value is map<partition name, 
partition item>
         Map<MTMVRelatedTableIf, Map<String, PartitionItem>> 
relatedPartitionItems = lastResult.getItems();
         for (Entry<MTMVRelatedTableIf, Map<String, PartitionItem>> entry : 
relatedPartitionItems.entrySet()) {
             int relatedColPos = mvPartitionInfo.getPctColPos(entry.getKey());
             Map<PartitionKeyDesc, Set<String>> onePctRes = Maps.newHashMap();
+            Set<String> queryUsedPartitions = 
queryUsedPartitionMap.get(entry.getKey().getFullQualifiers());
             for (Entry<String, PartitionItem> onePctEntry : 
entry.getValue().entrySet()) {
+                if (queryUsedPartitions != null && 
!queryUsedPartitions.contains(onePctEntry.getKey())) {
+                    continue;
+                }
                 PartitionKeyDesc partitionKeyDesc = 
onePctEntry.getValue().toPartitionKeyDesc(relatedColPos);
                 if (onePctRes.containsKey(partitionKeyDesc)) {
                     onePctRes.get(partitionKeyDesc).add(onePctEntry.getKey());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGenerator.java
index e20910fb571..cb95fd67840 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGenerator.java
@@ -42,7 +42,8 @@ public class MTMVRelatedPartitionDescRollUpGenerator 
implements MTMVRelatedParti
 
     @Override
     public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException {
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+                      Map<List<String>, Set<String>> queryUsedPartitionMap) 
throws AnalysisException {
         if (mvPartitionInfo.getPartitionType() != MTMVPartitionType.EXPR) {
             return;
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescSyncLimitGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescSyncLimitGenerator.java
index b66cf822516..3b48165ec41 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescSyncLimitGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescSyncLimitGenerator.java
@@ -37,6 +37,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Only focus on partial partitions of related tables
@@ -45,7 +46,8 @@ public class MTMVRelatedPartitionDescSyncLimitGenerator 
implements MTMVRelatedPa
 
     @Override
     public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException {
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+                      Map<List<String>, Set<String>> queryUsedPartitionMap) 
throws AnalysisException {
         Map<MTMVRelatedTableIf, Map<String, PartitionItem>> partitionItems = 
lastResult.getItems();
         MTMVPartitionSyncConfig config = 
generateMTMVPartitionSyncConfigByProperties(mvProperties);
         if (config.getSyncLimit() <= 0) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java
index 659889e11ca..06a88f03397 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescTransferGenerator.java
@@ -44,7 +44,8 @@ public class MTMVRelatedPartitionDescTransferGenerator 
implements MTMVRelatedPar
 
     @Override
     public void apply(MTMVPartitionInfo mvPartitionInfo, Map<String, String> 
mvProperties,
-            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns) throws AnalysisException {
+            RelatedPartitionDescResult lastResult, List<Column> 
partitionColumns,
+                      Map<List<String>, Set<String>> queryUsedPartitionMap) 
throws AnalysisException {
         Map<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> descs = 
lastResult.getDescs();
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> res = 
Maps.newHashMap();
         for (Entry<MTMVRelatedTableIf, Map<PartitionKeyDesc, Set<String>>> 
entry : descs.entrySet()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRewriteUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRewriteUtil.java
index 6dc0bd24da7..40414e9d641 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRewriteUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVRewriteUtil.java
@@ -75,7 +75,8 @@ public class MTMVRewriteUtil {
             }
             if (refreshContext == null) {
                 try {
-                    refreshContext = MTMVRefreshContext.buildContext(mtmv);
+                    refreshContext = MTMVRefreshContext.buildContext(mtmv,
+                            queryUsedPartitions != null ? queryUsedPartitions 
: Maps.newHashMap());
                 } catch (AnalysisException e) {
                     LOG.warn("buildContext failed", e);
                     // After failure, one should quickly return to avoid 
repeated failures
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContext.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContext.java
index 20d28036fcb..e936c04c803 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContext.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContext.java
@@ -40,6 +40,7 @@ import com.google.common.collect.Multimap;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -56,6 +57,7 @@ public class AsyncMaterializationContext extends 
MaterializationContext {
     private static final Logger LOG = 
LogManager.getLogger(AsyncMaterializationContext.class);
     private final MTMV mtmv;
     private Map<MTMVRelatedTableIf, Map<String, Set<String>>> 
partitionMultiFlatMap;
+    private final Map<List<String>, Set<String>> 
coveredQueryUsedBaseTablePartitionMap = new HashMap<>();
 
     /**
      * MaterializationContext, this contains necessary info for query 
rewriting by mv
@@ -184,12 +186,19 @@ public class AsyncMaterializationContext extends 
MaterializationContext {
     /**
      * Calculate partition mappings and cache
      */
-    public Map<MTMVRelatedTableIf, Map<String, Set<String>>> 
calculatePartitionMappings() throws AnalysisException {
-        if (partitionMultiFlatMap != null) {
+    public Map<MTMVRelatedTableIf, Map<String, Set<String>>> 
calculatePartitionMappings(
+            Map<List<String>, Set<String>> queryUsedBaseTablePartitionMap) 
throws AnalysisException {
+        Map<List<String>, Set<String>> effectiveQueryUsedBaseTablePartitionMap
+                = 
mtmv.getEffectiveQueryUsedBaseTablePartitionMap(queryUsedBaseTablePartitionMap);
+        Set<MTMVRelatedTableIf> pctTables = 
mtmv.getMvPartitionInfo().getPctTables();
+        if 
(isPartitionMappingsCovered(effectiveQueryUsedBaseTablePartitionMap, 
pctTables)) {
             return partitionMultiFlatMap;
         }
-        partitionMultiFlatMap = new HashMap<>();
-        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMultiMap = 
this.mtmv.calculatePartitionMappings();
+        if (partitionMultiFlatMap == null) {
+            partitionMultiFlatMap = new HashMap<>();
+        }
+        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMultiMap
+                = 
this.mtmv.calculatePartitionMappings(queryUsedBaseTablePartitionMap);
         for (Map.Entry<String, Map<MTMVRelatedTableIf, Set<String>>> entry : 
partitionMultiMap.entrySet()) {
             String partitionKey = entry.getKey();
             Map<MTMVRelatedTableIf, Set<String>> tableMap = entry.getValue();
@@ -202,6 +211,58 @@ public class AsyncMaterializationContext extends 
MaterializationContext {
                         .addAll(set);
             }
         }
+        mergeCoveredPartitions(effectiveQueryUsedBaseTablePartitionMap, 
pctTables);
         return partitionMultiFlatMap;
     }
+
+    private boolean isPartitionMappingsCovered(Map<List<String>, Set<String>> 
effectiveQueryUsedBaseTablePartitionMap,
+            Set<MTMVRelatedTableIf> pctTables) {
+        if (partitionMultiFlatMap == null) {
+            return false;
+        }
+        for (MTMVRelatedTableIf pctTable : pctTables) {
+            List<String> tableQualifiers = pctTable.getFullQualifiers();
+            Set<String> queryUsedPartitions = 
effectiveQueryUsedBaseTablePartitionMap.containsKey(tableQualifiers)
+                    ? 
effectiveQueryUsedBaseTablePartitionMap.get(tableQualifiers)
+                    : null;
+            if (!isTableCovered(tableQualifiers, queryUsedPartitions)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isTableCovered(List<String> tableQualifiers, Set<String> 
queryUsedPartitions) {
+        if 
(!coveredQueryUsedBaseTablePartitionMap.containsKey(tableQualifiers)) {
+            return false;
+        }
+        Set<String> coveredPartitions = 
coveredQueryUsedBaseTablePartitionMap.get(tableQualifiers);
+        if (coveredPartitions == null) {
+            return true;
+        }
+        return queryUsedPartitions != null && 
coveredPartitions.containsAll(queryUsedPartitions);
+    }
+
+    private void mergeCoveredPartitions(Map<List<String>, Set<String>> 
effectiveQueryUsedBaseTablePartitionMap,
+            Set<MTMVRelatedTableIf> pctTables) {
+        for (MTMVRelatedTableIf pctTable : pctTables) {
+            List<String> tableQualifiers = pctTable.getFullQualifiers();
+            Set<String> queryUsedPartitions = 
effectiveQueryUsedBaseTablePartitionMap.containsKey(tableQualifiers)
+                    ? 
effectiveQueryUsedBaseTablePartitionMap.get(tableQualifiers)
+                    : null;
+            List<String> copiedQualifiers = new ArrayList<>(tableQualifiers);
+            if (queryUsedPartitions == null) {
+                coveredQueryUsedBaseTablePartitionMap.put(copiedQualifiers, 
null);
+                continue;
+            }
+            if 
(!coveredQueryUsedBaseTablePartitionMap.containsKey(copiedQualifiers)) {
+                coveredQueryUsedBaseTablePartitionMap.put(copiedQualifiers, 
new HashSet<>(queryUsedPartitions));
+                continue;
+            }
+            Set<String> coveredPartitions = 
coveredQueryUsedBaseTablePartitionMap.get(copiedQualifiers);
+            if (coveredPartitions != null) {
+                coveredPartitions.addAll(queryUsedPartitions);
+            }
+        }
+    }
 }
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 7b07130aa59..a8613cf3dc6 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
@@ -102,7 +102,7 @@ public class PartitionCompensator {
                     
rewrittenPlanUsePartitionNameSet.add(olapScan.getTable().getPartition(id).getName()));
         }
         Map<MTMVRelatedTableIf, Map<String, Set<String>>> 
mtmvRelatedTableIfMapMap
-                = materializationContext.calculatePartitionMappings();
+                = 
materializationContext.calculatePartitionMappings(queryUsedBaseTablePartitionMap);
         boolean allCompensateIsNull = true;
 
         Map<BaseTableInfo, Set<String>> mvPartitionNeedRemoveNameMap = new 
HashMap<>();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVExpandPartitionTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVExpandPartitionTest.java
new file mode 100644
index 00000000000..145de3f4e57
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVExpandPartitionTest.java
@@ -0,0 +1,269 @@
+// 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.mtmv;
+
+import org.apache.doris.analysis.PartitionValue;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.PartitionItem;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PartitionType;
+import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.common.AnalysisException;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Range;
+import com.google.common.collect.Sets;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for {@link MTMVPartitionExpander#expandToMvPartitionGranularity}.
+ * Scenario: base table has daily RANGE partitions, MV has monthly RANGE 
partitions.
+ * The expansion should map a queried daily partition to ALL daily partitions 
within
+ * the same MV (monthly) partition range, ensuring complete partition mappings 
for
+ * isSyncWithPartitions correctness while reducing the number of partitions 
processed
+ * by the rollup pipeline.
+ * Uses Java dynamic proxy to create lightweight MTMVRelatedTableIf mocks
+ * without triggering class-loading of the full TableIf hierarchy.
+ */
+public class MTMVExpandPartitionTest {
+
+    private static final Column DATE_COL = new Column("c1", 
ScalarType.createType(PrimitiveType.DATE),
+            true, null, "", "");
+
+    private static final List<String> RANGE_TABLE_QUALIFIERS = 
Lists.newArrayList("internal", "db", "daily_base");
+    private static final List<String> LIST_TABLE_QUALIFIERS = 
Lists.newArrayList("internal", "db", "list_base");
+
+    private MTMVRelatedTableIf rangeTable;
+    private MTMVRelatedTableIf listTable;
+
+    private Map<String, PartitionItem> dailyBasePartitions;
+    private Map<String, PartitionItem> monthlyMvPartitions;
+
+    @Before
+    public void setUp() throws Exception {
+        dailyBasePartitions = Maps.newHashMap();
+        dailyBasePartitions.put("p20210101", buildRange("2021-01-01", 
"2021-01-02"));
+        dailyBasePartitions.put("p20210102", buildRange("2021-01-02", 
"2021-01-03"));
+        dailyBasePartitions.put("p20210103", buildRange("2021-01-03", 
"2021-01-04"));
+        dailyBasePartitions.put("p20210201", buildRange("2021-02-01", 
"2021-02-02"));
+        dailyBasePartitions.put("p20210202", buildRange("2021-02-02", 
"2021-02-03"));
+
+        monthlyMvPartitions = Maps.newHashMap();
+        monthlyMvPartitions.put("mv_202101", buildRange("2021-01-01", 
"2021-02-01"));
+        monthlyMvPartitions.put("mv_202102", buildRange("2021-02-01", 
"2021-03-01"));
+
+        rangeTable = createMockTable(RANGE_TABLE_QUALIFIERS, 
PartitionType.RANGE, dailyBasePartitions);
+        listTable = createMockTable(LIST_TABLE_QUALIFIERS, PartitionType.LIST, 
Maps.newHashMap());
+    }
+
+    @Test
+    public void testExpandSinglePartitionToMonth() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p20210102"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(rangeTable));
+
+        Set<String> expanded = result.get(RANGE_TABLE_QUALIFIERS);
+        Assert.assertNotNull(expanded);
+        Assert.assertEquals(Sets.newHashSet("p20210101", "p20210102", 
"p20210103"), expanded);
+    }
+
+    @Test
+    public void testExpandMultipleMonths() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p20210101", 
"p20210202"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(rangeTable));
+
+        Set<String> expanded = result.get(RANGE_TABLE_QUALIFIERS);
+        Assert.assertNotNull(expanded);
+        Assert.assertEquals(
+                Sets.newHashSet("p20210101", "p20210102", "p20210103", 
"p20210201", "p20210202"),
+                expanded);
+    }
+
+    @Test
+    public void testMultiplePartitionsSelectSameMvRange() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p20210101", 
"p20210102"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(rangeTable));
+
+        Assert.assertEquals(Sets.newHashSet("p20210101", "p20210102", 
"p20210103"),
+                result.get(RANGE_TABLE_QUALIFIERS));
+    }
+
+    @Test
+    public void testRangeGapDoesNotMatch() throws Exception {
+        Map<String, PartitionItem> mvPartitionsWithGap = Maps.newHashMap();
+        mvPartitionsWithGap.put("mv_202101", buildRange("2021-01-01", 
"2021-02-01"));
+        mvPartitionsWithGap.put("mv_202103", buildRange("2021-03-01", 
"2021-04-01"));
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p20210201"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, mvPartitionsWithGap, Sets.newHashSet(rangeTable));
+
+        Assert.assertTrue(result.get(RANGE_TABLE_QUALIFIERS).isEmpty());
+    }
+
+    @Test
+    public void testManyPartitionsSparseAndBroadFilters() throws Exception {
+        Map<String, PartitionItem> basePartitions = Maps.newHashMap();
+        Map<String, PartitionItem> mvPartitions = Maps.newHashMap();
+        LocalDate start = LocalDate.of(2021, 1, 1);
+        for (int i = 0; i < 360; i++) {
+            basePartitions.put("p" + i, 
buildRange(start.plusDays(i).toString(),
+                    start.plusDays(i + 1).toString()));
+        }
+        for (int i = 0; i < 12; i++) {
+            mvPartitions.put("mv" + i, buildRange(start.plusDays(i * 
30L).toString(),
+                    start.plusDays((i + 1) * 30L).toString()));
+        }
+        MTMVRelatedTableIf manyPartitionTable = createMockTable(
+                RANGE_TABLE_QUALIFIERS, PartitionType.RANGE, basePartitions);
+        Map<List<String>, Set<String>> sparseFilter = Maps.newHashMap();
+        sparseFilter.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p45"));
+
+        Map<List<String>, Set<String>> sparseResult = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                sparseFilter, mvPartitions, 
Sets.newHashSet(manyPartitionTable));
+
+        Assert.assertEquals(30, 
sparseResult.get(RANGE_TABLE_QUALIFIERS).size());
+        
Assert.assertTrue(sparseResult.get(RANGE_TABLE_QUALIFIERS).contains("p30"));
+        
Assert.assertTrue(sparseResult.get(RANGE_TABLE_QUALIFIERS).contains("p59"));
+
+        Map<List<String>, Set<String>> broadFilter = Maps.newHashMap();
+        broadFilter.put(RANGE_TABLE_QUALIFIERS, basePartitions.keySet());
+        Map<List<String>, Set<String>> broadResult = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                broadFilter, mvPartitions, 
Sets.newHashSet(manyPartitionTable));
+
+        Assert.assertEquals(basePartitions.keySet(), 
broadResult.get(RANGE_TABLE_QUALIFIERS));
+    }
+
+    @Test
+    public void testListPartitionPassthrough() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(LIST_TABLE_QUALIFIERS, Sets.newHashSet("p1"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(listTable));
+
+        Set<String> expanded = result.get(LIST_TABLE_QUALIFIERS);
+        Assert.assertNotNull(expanded);
+        Assert.assertEquals(Sets.newHashSet("p1"), expanded);
+    }
+
+    @Test
+    public void testNonExistentPartition() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, 
Sets.newHashSet("p_nonexistent"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(rangeTable));
+
+        Set<String> expanded = result.get(RANGE_TABLE_QUALIFIERS);
+        Assert.assertNotNull(expanded);
+        Assert.assertTrue(expanded.isEmpty());
+    }
+
+    @Test
+    public void testBasePartitionOutsideMvRange() throws Exception {
+        Map<String, PartitionItem> janOnlyMv = Maps.newHashMap();
+        janOnlyMv.put("mv_202101", buildRange("2021-01-01", "2021-02-01"));
+
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(RANGE_TABLE_QUALIFIERS, Sets.newHashSet("p20210101"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, janOnlyMv, Sets.newHashSet(rangeTable));
+
+        Set<String> expanded = result.get(RANGE_TABLE_QUALIFIERS);
+        Assert.assertNotNull(expanded);
+        Assert.assertEquals(Sets.newHashSet("p20210101", "p20210102", 
"p20210103"), expanded);
+    }
+
+    @Test
+    public void testPctTableNotInFilter() throws Exception {
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(Lists.newArrayList("internal", "db", "other_table"),
+                Sets.newHashSet("p20210101"));
+
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                queryUsed, monthlyMvPartitions, Sets.newHashSet(rangeTable));
+
+        Assert.assertNull(result.get(RANGE_TABLE_QUALIFIERS));
+    }
+
+    @Test
+    public void testEmptyFilter() throws Exception {
+        Map<List<String>, Set<String>> result = 
MTMVPartitionExpander.expandToMvPartitionGranularity(
+                Maps.newHashMap(), monthlyMvPartitions, 
Sets.newHashSet(rangeTable));
+
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    // --- helpers ---
+
+    private static MTMVRelatedTableIf createMockTable(List<String> qualifiers,
+            PartitionType partitionType, Map<String, PartitionItem> 
partitionItems) {
+        InvocationHandler handler = (proxy, method, args) -> {
+            switch (method.getName()) {
+                case "getFullQualifiers":
+                    return qualifiers;
+                case "getPartitionType":
+                    return partitionType;
+                case "getAndCopyPartitionItems":
+                    return partitionItems;
+                case "hashCode":
+                    return System.identityHashCode(proxy);
+                case "equals":
+                    return proxy == args[0];
+                default:
+                    throw new UnsupportedOperationException(
+                            "MTMVExpandPartitionTest mock does not support: " 
+ method.getName());
+            }
+        };
+        return (MTMVRelatedTableIf) Proxy.newProxyInstance(
+                MTMVRelatedTableIf.class.getClassLoader(),
+                new Class<?>[] {MTMVRelatedTableIf.class},
+                handler);
+    }
+
+    private static RangePartitionItem buildRange(String lower, String upper) 
throws AnalysisException {
+        PartitionKey lowerKey = PartitionKey.createPartitionKey(
+                Lists.newArrayList(new PartitionValue(lower)), 
Lists.newArrayList(DATE_COL));
+        PartitionKey upperKey = PartitionKey.createPartitionKey(
+                Lists.newArrayList(new PartitionValue(upper)), 
Lists.newArrayList(DATE_COL));
+        return new RangePartitionItem(Range.closedOpen(lowerKey, upperKey));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPartitionUtilTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPartitionUtilTest.java
index 41ca743e76a..80f91de7912 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPartitionUtilTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVPartitionUtilTest.java
@@ -68,7 +68,8 @@ public class MTMVPartitionUtilTest {
 
         mtmvUtilStatic = Mockito.mockStatic(MTMVUtil.class);
         refreshContextStatic = Mockito.mockStatic(MTMVRefreshContext.class);
-        refreshContextStatic.when(() -> 
MTMVRefreshContext.buildContext(Mockito.any(MTMV.class))).thenReturn(context);
+        refreshContextStatic.when(() -> 
MTMVRefreshContext.buildContext(Mockito.any(MTMV.class), Mockito.anyMap()))
+                .thenReturn(context);
 
         Mockito.when(mtmv.getRelation()).thenReturn(relation);
 
@@ -252,6 +253,34 @@ public class MTMVPartitionUtilTest {
         Assert.assertFalse(MTMVPartitionUtil.isTableNamelike(new 
TableNameInfo("ctl1"), tableNameToCheck));
     }
 
+    @Test
+    public void testGetBaseVersionsUsesMappedPartitions() throws 
AnalysisException {
+        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMappings = 
Maps.newHashMap();
+        partitionMappings.put("mv1", pctMapping("p1"));
+
+        assertFetchedPartitionNames(partitionMappings, Sets.newHashSet("p1", 
"p2", "p3"),
+                Sets.newHashSet("p1"));
+    }
+
+    @Test
+    public void testGetBaseVersionsDeduplicatesMappedPartitions() throws 
AnalysisException {
+        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMappings = 
Maps.newHashMap();
+        partitionMappings.put("mv1", pctMapping("p1", "p2"));
+        partitionMappings.put("mv2", pctMapping("p2", "p3"));
+
+        assertFetchedPartitionNames(partitionMappings, Sets.newHashSet("p1", 
"p2", "p3", "p4"),
+                Sets.newHashSet("p1", "p2", "p3"));
+    }
+
+    @Test
+    public void testGetBaseVersionsUsesAllFullyMappedPartitions() throws 
AnalysisException {
+        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMappings = 
Maps.newHashMap();
+        partitionMappings.put("mv1", pctMapping("p1", "p2", "p3"));
+
+        assertFetchedPartitionNames(partitionMappings, Sets.newHashSet("p1", 
"p2", "p3"),
+                Sets.newHashSet("p1", "p2", "p3"));
+    }
+
     @Test
     public void testGetTableSnapshotFromContext() throws AnalysisException {
         Map<BaseTableInfo, MTMVSnapshotIf> cache = Maps.newHashMap();
@@ -261,4 +290,51 @@ public class MTMVPartitionUtilTest {
         Assert.assertEquals(1, cache.size());
         Assert.assertEquals(baseSnapshotIf, cache.values().iterator().next());
     }
+
+    private Map<MTMVRelatedTableIf, Set<String>> pctMapping(String... 
partitionNames) {
+        Map<MTMVRelatedTableIf, Set<String>> mapping = Maps.newHashMap();
+        mapping.put(baseOlapTable, Sets.newHashSet(partitionNames));
+        return mapping;
+    }
+
+    private void assertFetchedPartitionNames(
+            Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
partitionMappings,
+            Set<String> allPartitionNames, Set<String> expectedPartitionNames) 
throws AnalysisException {
+        Mockito.when(mtmv.getRelation()).thenReturn(null);
+        
Mockito.when(mtmvPartitionInfo.getPartitionType()).thenReturn(MTMVPartitionType.FOLLOW_BASE_TABLE);
+        
Mockito.when(mtmvPartitionInfo.getPctTables()).thenReturn(Sets.newHashSet(baseOlapTable));
+
+        Map<String, Partition> partitions = Maps.newHashMap();
+        long visibleVersion = 1;
+        for (String partitionName : allPartitionNames) {
+            Partition partition = Mockito.mock(Partition.class);
+            Mockito.when(partition.getName()).thenReturn(partitionName);
+            
Mockito.when(partition.getVisibleVersion()).thenReturn(visibleVersion++);
+            
Mockito.when(baseOlapTable.getPartitionOrAnalysisException(partitionName)).thenReturn(partition);
+            partitions.put(partitionName, partition);
+        }
+        
Mockito.when(baseOlapTable.getPartitions()).thenReturn(partitions.values());
+
+        List<Set<String>> versionRequests = Lists.newArrayList();
+        try (MockedStatic<Partition> partitionStatic = 
Mockito.mockStatic(Partition.class, Mockito.CALLS_REAL_METHODS)) {
+            partitionStatic.when(() -> 
Partition.getVisibleVersions(Mockito.anyList())).thenAnswer(invocation -> {
+                List<? extends Partition> requestedPartitions = 
invocation.getArgument(0);
+                Set<String> requestedPartitionNames = Sets.newHashSet();
+                List<Long> visibleVersions = Lists.newArrayList();
+                for (Partition partition : requestedPartitions) {
+                    requestedPartitionNames.add(partition.getName());
+                    visibleVersions.add(partition.getVisibleVersion());
+                }
+                versionRequests.add(requestedPartitionNames);
+                return visibleVersions;
+            });
+
+            Assert.assertEquals(expectedPartitionNames,
+                    MTMVPartitionUtil.getBaseVersions(mtmv, partitionMappings)
+                            .getPartitionVersions(baseOlapTable).keySet());
+        }
+        Assert.assertEquals(1, versionRequests.size());
+        Assert.assertEquals(expectedPartitionNames, versionRequests.get(0));
+        Mockito.verify(baseOlapTable, Mockito.never()).getPartitions();
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorTest.java
index d60f4be7f8f..ab158432866 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescGeneratorTest.java
@@ -32,6 +32,7 @@ import org.apache.doris.utframe.TestWithFeService;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -106,7 +107,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 3 partition
         Assertions.assertEquals(3, partitionKeyDescMap.size());
         OlapTable t1 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -119,6 +120,40 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         }
     }
 
+    @Test
+    public void testQueryUsedPartitionsRange() throws Exception {
+        MTMVPartitionInfo mtmvPartitionInfo = 
getMTMVPartitionInfo(Lists.newArrayList("t1"));
+        Column c1Column = new Column("c1", PrimitiveType.DATE);
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(Lists.newArrayList("internal", "test", "t1"), 
Sets.newHashSet("p20210201"));
+
+        Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
+                = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
+                Lists.newArrayList(c1Column), queryUsed);
+        Assertions.assertEquals(1, partitionKeyDescMap.size());
+
+        Set<String> collected = Sets.newHashSet();
+        partitionKeyDescMap.values().forEach(m -> 
m.values().forEach(collected::addAll));
+        Assertions.assertEquals(Sets.newHashSet("p20210201"), collected);
+    }
+
+    @Test
+    public void testQueryUsedPartitionsList() throws Exception {
+        MTMVPartitionInfo mtmvPartitionInfo = 
getMTMVPartitionInfo(Lists.newArrayList("t2"));
+        Column c1Column = new Column("c1", PrimitiveType.INT);
+        Map<List<String>, Set<String>> queryUsed = Maps.newHashMap();
+        queryUsed.put(Lists.newArrayList("internal", "test", "t2"), 
Sets.newHashSet("p1_bj"));
+
+        Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
+                = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
+                Lists.newArrayList(c1Column), queryUsed);
+        Assertions.assertEquals(1, partitionKeyDescMap.size());
+
+        Set<String> collected = Sets.newHashSet();
+        partitionKeyDescMap.values().forEach(m -> 
m.values().forEach(collected::addAll));
+        Assertions.assertEquals(Sets.newHashSet("p1_bj"), collected);
+    }
+
     @Test
     public void testLimit() throws Exception {
         MTMVPartitionInfo mtmvPartitionInfo = 
getMTMVPartitionInfo(Lists.newArrayList("t1"));
@@ -127,7 +162,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         mvProperty.put(PropertyAnalyzer.PROPERTIES_PARTITION_SYNC_LIMIT, "1");
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, mvProperty,
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 3 partition
         Assertions.assertEquals(1, partitionKeyDescMap.size());
         OlapTable t1 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -146,7 +181,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.INT);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 3 partition
         Assertions.assertEquals(2, partitionKeyDescMap.size());
         OlapTable t2 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -172,7 +207,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.INT);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 2 partition
         Assertions.assertEquals(2, partitionKeyDescMap.size());
         OlapTable t1 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -190,7 +225,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Assertions.assertThrows(AnalysisException.class,
                 () -> 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                        Lists.newArrayList(c1Column)));
+                        Lists.newArrayList(c1Column), Maps.newHashMap()));
     }
 
     @Test
@@ -199,7 +234,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Assertions.assertThrows(AnalysisException.class,
                 () -> 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                        Lists.newArrayList(c1Column)));
+                        Lists.newArrayList(c1Column), Maps.newHashMap()));
     }
 
     @Test
@@ -208,7 +243,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 4 partition
         Assertions.assertEquals(4, partitionKeyDescMap.size());
         boolean hasOne = false;
@@ -232,7 +267,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.INT);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 2 partition
         Assertions.assertEquals(2, partitionKeyDescMap.size());
         OlapTable t2 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -252,7 +287,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Map<PartitionKeyDesc, Map<MTMVRelatedTableIf, Set<String>>> 
partitionKeyDescMap
                 = 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                Lists.newArrayList(c1Column));
+                Lists.newArrayList(c1Column), Maps.newHashMap());
         // 2 partition
         Assertions.assertEquals(2, partitionKeyDescMap.size());
         OlapTable t6 = (OlapTable) 
Env.getCurrentEnv().getInternalCatalog().getDbOrAnalysisException("test")
@@ -272,7 +307,7 @@ public class MTMVRelatedPartitionDescGeneratorTest extends 
TestWithFeService {
         Column c1Column = new Column("c1", PrimitiveType.DATE);
         Assertions.assertThrows(AnalysisException.class,
                 () -> 
MTMVPartitionUtil.generateRelatedPartitionDescs(mtmvPartitionInfo, 
Maps.newHashMap(),
-                        Lists.newArrayList(c1Column)));
+                        Lists.newArrayList(c1Column), Maps.newHashMap()));
     }
 
     private MTMVPartitionInfo getMTMVPartitionInfo(List<String> pctTableNames) 
throws AnalysisException {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContextTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContextTest.java
new file mode 100644
index 00000000000..2490cead969
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/AsyncMaterializationContextTest.java
@@ -0,0 +1,136 @@
+// 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.exploration.mv;
+
+import org.apache.doris.catalog.MTMV;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.mtmv.MTMVPartitionInfo;
+import org.apache.doris.mtmv.MTMVRelatedTableIf;
+import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.StatementContext;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.plans.Plan;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class AsyncMaterializationContextTest {
+
+    @Test
+    public void testCalculatePartitionMappingsMergesUncoveredFilters() throws 
Exception {
+        List<String> qualifiers = ImmutableList.of("ctl", "db", "t1");
+        MTMVRelatedTableIf pctTable = mockPctTable(qualifiers);
+        MTMV mtmv = mockMtmv(pctTable);
+        AsyncMaterializationContext context = createContext(mtmv);
+
+        Map<List<String>, Set<String>> queryUsedPartitionsA = 
Maps.newHashMap();
+        queryUsedPartitionsA.put(qualifiers, ImmutableSet.of("p1"));
+        Map<List<String>, Set<String>> queryUsedPartitionsB = 
Maps.newHashMap();
+        queryUsedPartitionsB.put(qualifiers, ImmutableSet.of("p2"));
+
+        
Mockito.when(mtmv.getEffectiveQueryUsedBaseTablePartitionMap(queryUsedPartitionsA))
+                .thenReturn(queryUsedPartitionsA);
+        
Mockito.when(mtmv.getEffectiveQueryUsedBaseTablePartitionMap(queryUsedPartitionsB))
+                .thenReturn(queryUsedPartitionsB);
+        Mockito.when(mtmv.calculatePartitionMappings(queryUsedPartitionsA))
+                .thenReturn(buildPartitionMultiMap("mv_p1", pctTable, "p1"));
+        Mockito.when(mtmv.calculatePartitionMappings(queryUsedPartitionsB))
+                .thenReturn(buildPartitionMultiMap("mv_p2", pctTable, "p2"));
+
+        Map<MTMVRelatedTableIf, Map<String, Set<String>>> resultA
+                = context.calculatePartitionMappings(queryUsedPartitionsA);
+        Map<MTMVRelatedTableIf, Map<String, Set<String>>> resultB
+                = context.calculatePartitionMappings(queryUsedPartitionsB);
+
+        Assertions.assertEquals(ImmutableSet.of("p1"), 
resultA.get(pctTable).get("mv_p1"));
+        Assertions.assertEquals(ImmutableSet.of("p1"), 
resultB.get(pctTable).get("mv_p1"));
+        Assertions.assertEquals(ImmutableSet.of("p2"), 
resultB.get(pctTable).get("mv_p2"));
+        Mockito.verify(mtmv).calculatePartitionMappings(queryUsedPartitionsA);
+        Mockito.verify(mtmv).calculatePartitionMappings(queryUsedPartitionsB);
+    }
+
+    @Test
+    public void testCalculatePartitionMappingsReusesCoverageAfterFullMapping() 
throws Exception {
+        List<String> qualifiers = ImmutableList.of("ctl", "db", "t1");
+        MTMVRelatedTableIf pctTable = mockPctTable(qualifiers);
+        MTMV mtmv = mockMtmv(pctTable);
+        AsyncMaterializationContext context = createContext(mtmv);
+
+        Map<List<String>, Set<String>> noFilter = Maps.newHashMap();
+        Map<List<String>, Set<String>> queryUsedPartitions = Maps.newHashMap();
+        queryUsedPartitions.put(qualifiers, ImmutableSet.of("p1"));
+
+        
Mockito.when(mtmv.getEffectiveQueryUsedBaseTablePartitionMap(noFilter)).thenReturn(noFilter);
+        
Mockito.when(mtmv.getEffectiveQueryUsedBaseTablePartitionMap(queryUsedPartitions))
+                .thenReturn(queryUsedPartitions);
+        Mockito.when(mtmv.calculatePartitionMappings(noFilter))
+                .thenReturn(buildPartitionMultiMap("mv_p1", pctTable, "p1"));
+
+        Map<MTMVRelatedTableIf, Map<String, Set<String>>> resultA = 
context.calculatePartitionMappings(noFilter);
+        Map<MTMVRelatedTableIf, Map<String, Set<String>>> resultB
+                = context.calculatePartitionMappings(queryUsedPartitions);
+
+        Assertions.assertSame(resultA, resultB);
+        Assertions.assertEquals(ImmutableSet.of("p1"), 
resultB.get(pctTable).get("mv_p1"));
+        Mockito.verify(mtmv).calculatePartitionMappings(noFilter);
+        Mockito.verify(mtmv, 
Mockito.never()).calculatePartitionMappings(queryUsedPartitions);
+    }
+
+    private static AsyncMaterializationContext createContext(MTMV mtmv) {
+        CascadesContext cascadesContext = Mockito.mock(CascadesContext.class);
+        StatementContext statementContext = 
Mockito.mock(StatementContext.class);
+        
Mockito.when(cascadesContext.getStatementContext()).thenReturn(statementContext);
+        StructInfo structInfo = Mockito.mock(StructInfo.class);
+        
Mockito.doReturn(Collections.<Expression>emptyList()).when(structInfo).getPlanOutputShuttledExpressions();
+        Plan plan = Mockito.mock(Plan.class);
+        return new AsyncMaterializationContext(mtmv, plan, plan, 
ImmutableList.of(), ImmutableList.of(),
+                cascadesContext, structInfo);
+    }
+
+    private static MTMV mockMtmv(MTMVRelatedTableIf pctTable) throws 
AnalysisException {
+        MTMV mtmv = Mockito.mock(MTMV.class);
+        MTMVPartitionInfo mtmvPartitionInfo = 
Mockito.mock(MTMVPartitionInfo.class);
+        Mockito.when(mtmv.getMvPartitionInfo()).thenReturn(mtmvPartitionInfo);
+        
Mockito.when(mtmvPartitionInfo.getPctTables()).thenReturn(ImmutableSet.of(pctTable));
+        return mtmv;
+    }
+
+    private static MTMVRelatedTableIf mockPctTable(List<String> qualifiers) {
+        MTMVRelatedTableIf pctTable = Mockito.mock(MTMVRelatedTableIf.class);
+        Mockito.when(pctTable.getFullQualifiers()).thenReturn(qualifiers);
+        return pctTable;
+    }
+
+    private static Map<String, Map<MTMVRelatedTableIf, Set<String>>> 
buildPartitionMultiMap(
+            String mvPartitionName, MTMVRelatedTableIf pctTable, String 
basePartitionName) {
+        Map<String, Map<MTMVRelatedTableIf, Set<String>>> partitionMultiMap = 
Maps.newHashMap();
+        Map<MTMVRelatedTableIf, Set<String>> relatedTableToPartitions = 
Maps.newHashMap();
+        relatedTableToPartitions.put(pctTable, 
ImmutableSet.of(basePartitionName));
+        partitionMultiMap.put(mvPartitionName, relatedTableToPartitions);
+        return partitionMultiMap;
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensatorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensatorTest.java
index 2d6cb15864a..0ad6d8b49ed 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensatorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/PartitionCompensatorTest.java
@@ -449,7 +449,7 @@ public class PartitionCompensatorTest extends 
TestWithFeService {
 
         AsyncMaterializationContext matCtx = 
Mockito.mock(AsyncMaterializationContext.class);
         Mockito.when(matCtx.getMtmv()).thenReturn(mtmv);
-        
Mockito.when(matCtx.calculatePartitionMappings()).thenReturn(partitionMappings);
+        
Mockito.when(matCtx.calculatePartitionMappings(ArgumentMatchers.any())).thenReturn(partitionMappings);
 
         // StatementContext: the MV's two valid partitions are available for 
rewrite
         Map<BaseTableInfo, Collection<Partition>> canRewriteMap = new 
HashMap<>();
@@ -539,7 +539,7 @@ public class PartitionCompensatorTest extends 
TestWithFeService {
 
         AsyncMaterializationContext matCtx = 
Mockito.mock(AsyncMaterializationContext.class);
         Mockito.when(matCtx.getMtmv()).thenReturn(mtmv);
-        
Mockito.when(matCtx.calculatePartitionMappings()).thenReturn(partitionMappings);
+        
Mockito.when(matCtx.calculatePartitionMappings(ArgumentMatchers.any())).thenReturn(partitionMappings);
 
         Map<BaseTableInfo, Collection<Partition>> canRewriteMap = new 
HashMap<>();
         canRewriteMap.put(new BaseTableInfo(mtmv),


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

Reply via email to