This is an automated email from the ASF dual-hosted git repository.

seawinde 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 c0abfa4083f [fix](mtmv) Refresh MTMV after excluded trigger tables 
change (#64041)
c0abfa4083f is described below

commit c0abfa4083fea8e64df158e74ab58b7190e8db55
Author: seawinde <[email protected]>
AuthorDate: Fri Jun 12 07:39:55 2026 +0800

    [fix](mtmv) Refresh MTMV after excluded trigger tables change (#64041)
    
    Changing the effective `excluded_trigger_tables` set changes which base
    tables participate in MTMV freshness decisions. Reusing the old
    `refreshSnapshot` can make a later AUTO refresh skip work under the new
    exclude rules, leaving MV data based on the old snapshot.
    
    Root cause: In `MTMVTask.calculateNeedRefreshPartitions()`, AUTO refresh
    calls `MTMVPartitionUtil.isMTMVSync()` before verifying whether the
    refresh baseline is still complete. After `ALTER MATERIALIZED VIEW ...
    SET ("excluded_trigger_tables"=...)`, the new exclude rules can skip
    changed base tables and incorrectly mark the MV as fresh.
    
    | File | Change Description |
    |------|-------------------|
    | `MTMV.java` | Detect effective `excluded_trigger_tables` set changes,
    bump `schemaChangeVersion`, and clear `refreshSnapshot` without setting
    `MTMVStatus.state` to `SCHEMA_CHANGE`. |
    | `MTMVTask.java` | Treat incomplete refresh snapshots as requiring a
    complete AUTO refresh before calling `isMTMVSync()`. Manual
    specified-partition refresh keeps existing behavior. |
    | `MTMVTest.java` | Cover qualified-name changes, same-set order
    changes, reduced/cleared excluded table sets, and unrelated property
    changes. |
    | `MTMVTaskTest.java` | Cover incomplete-baseline AUTO refresh and
    manual specified-partition behavior. |
    | `test_excluded_trigger_table_mtmv.groovy` | Align the case comments
    with the expected refresh behavior. |
    
    Design rationale:
    This uses the existing `refreshSnapshot` completeness as the
    invalid-baseline signal instead of adding a new persisted flag. That
    keeps the change scoped to the ALTER property path and AUTO refresh
    decision path. Query rewrite behavior is unchanged.
---
 .../main/java/org/apache/doris/catalog/MTMV.java   |  47 +++++++--
 .../apache/doris/job/extensions/mtmv/MTMVTask.java |   5 +
 .../java/org/apache/doris/mtmv/MTMVTaskTest.java   |  30 ++++++
 .../test/java/org/apache/doris/mtmv/MTMVTest.java  | 105 +++++++++++++++++++++
 .../data/mtmv_p0/test_create_mtmv_with_view.out    |   3 +-
 .../mtmv_p0/test_excluded_trigger_table_mtmv.out   |   6 ++
 .../test_excluded_trigger_table_mtmv.groovy        |   6 +-
 7 files changed, 189 insertions(+), 13 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 b67894b2be9..c760d63563e 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
@@ -280,7 +280,21 @@ public class MTMV extends OlapTable {
     public Map<String, String> alterMvProperties(Map<String, String> 
mvProperties) {
         writeMvLock();
         try {
+            boolean containsExcludedTriggerTables = mvProperties.containsKey(
+                    PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES);
+            Set<TableNameInfo> oldExcludedTriggerTables = 
containsExcludedTriggerTables
+                    ? parseExcludedTriggerTables()
+                    : Sets.newHashSet();
             this.mvProperties.putAll(mvProperties);
+            if (containsExcludedTriggerTables) {
+                Set<TableNameInfo> newExcludedTriggerTables = 
parseExcludedTriggerTables();
+                if 
(!oldExcludedTriggerTables.equals(newExcludedTriggerTables)) {
+                    // excluded_trigger_tables changes the refresh baseline 
semantics. Invalidate the old
+                    // snapshots so the next AUTO refresh rebuilds a complete 
baseline with the new rules.
+                    this.schemaChangeVersion++;
+                    this.refreshSnapshot = new MTMVRefreshSnapshot();
+                }
+            }
             return this.mvProperties;
         } finally {
             writeMvUnlock();
@@ -341,22 +355,26 @@ public class MTMV extends OlapTable {
     }
 
     public Set<TableNameInfo> getExcludedTriggerTables() {
-        Set<TableNameInfo> res = Sets.newHashSet();
         readMvLock();
         try {
-            if 
(StringUtils.isEmpty(mvProperties.get(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES)))
 {
-                return res;
-            }
-            String[] split = 
mvProperties.get(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES).split(",");
-            for (String alias : split) {
-                res.add(new TableNameInfo(alias));
-            }
-            return res;
+            return parseExcludedTriggerTables();
         } finally {
             readMvUnlock();
         }
     }
 
+    private Set<TableNameInfo> parseExcludedTriggerTables() {
+        Set<TableNameInfo> res = Sets.newHashSet();
+        if 
(StringUtils.isEmpty(mvProperties.get(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES)))
 {
+            return res;
+        }
+        String[] split = 
mvProperties.get(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES).split(",");
+        for (String alias : split) {
+            res.add(new TableNameInfo(alias));
+        }
+        return res;
+    }
+
     public Set<TableNameInfo> getQueryRewriteConsistencyRelaxedTables() {
         Set<TableNameInfo> res = Sets.newHashSet();
         readMvLock();
@@ -448,6 +466,17 @@ public class MTMV extends OlapTable {
         return refreshSnapshot;
     }
 
+    public boolean hasCompleteRefreshSnapshot() {
+        Set<String> partitionNames = getPartitionNames();
+        readMvLock();
+        try {
+            // A refresh baseline is complete only when every current MV 
partition has a snapshot.
+            return 
refreshSnapshot.getPartitionSnapshots().keySet().containsAll(partitionNames);
+        } finally {
+            readMvUnlock();
+        }
+    }
+
     public long getSchemaChangeVersion() {
         readMvLock();
         try {
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 7c9e65906ac..9766a175a56 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
@@ -645,6 +645,11 @@ public class MTMVTask extends AbstractTask {
         if (mtmv.getRefreshInfo().getRefreshMethod() == 
RefreshMethod.COMPLETE) {
             return Lists.newArrayList(mtmv.getPartitionNames());
         }
+        // An incomplete baseline cannot be checked by isMTMVSync, because the 
current exclude rules may
+        // skip the changed base tables and incorrectly mark the MV as fresh. 
Rebuild it with a full refresh.
+        if (!mtmv.hasCompleteRefreshSnapshot()) {
+            return Lists.newArrayList(mtmv.getPartitionNames());
+        }
         // check if data is fresh
         // We need to use a newly generated relationship and cannot retrieve 
it using mtmv.getRelation()
         // to avoid rebuilding the baseTable and causing a change in the 
tableId
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTaskTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTaskTest.java
index 505f210fc2f..f0bf165b2db 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTaskTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTaskTest.java
@@ -85,6 +85,8 @@ public class MTMVTaskTest {
         Mockito.when(mtmv.getRefreshInfo()).thenReturn(mtmvRefreshInfo);
 
         
Mockito.when(mtmvRefreshInfo.getRefreshMethod()).thenReturn(RefreshMethod.COMPLETE);
+
+        Mockito.when(mtmv.hasCompleteRefreshSnapshot()).thenReturn(true);
     }
 
     @After
@@ -127,6 +129,34 @@ public class MTMVTaskTest {
         Assert.assertEquals(allPartitionNames, result);
     }
 
+    @Test
+    public void 
testCalculateNeedRefreshPartitionsSystemIncompleteRefreshSnapshot() throws 
AnalysisException {
+        
Mockito.when(mtmvRefreshInfo.getRefreshMethod()).thenReturn(RefreshMethod.AUTO);
+        Mockito.when(mtmv.hasCompleteRefreshSnapshot()).thenReturn(false);
+
+        MTMVTaskContext context = new 
MTMVTaskContext(MTMVTaskTriggerMode.SYSTEM);
+        MTMVTask task = new MTMVTask(mtmv, relation, context);
+        List<String> result = task.calculateNeedRefreshPartitions(null);
+
+        Assert.assertEquals(allPartitionNames, result);
+        mtmvPartitionUtilStatic.verify(() -> MTMVPartitionUtil.isMTMVSync(
+                Mockito.nullable(MTMVRefreshContext.class), 
Mockito.nullable(Set.class), Mockito.nullable(Set.class)),
+                Mockito.never());
+    }
+
+    @Test
+    public void 
testCalculateNeedRefreshPartitionsManualPartitionsIncompleteRefreshSnapshot()
+            throws AnalysisException {
+        Mockito.when(mtmv.hasCompleteRefreshSnapshot()).thenReturn(false);
+
+        MTMVTaskContext context = new 
MTMVTaskContext(MTMVTaskTriggerMode.MANUAL, Lists.newArrayList(poneName),
+                false, null);
+        MTMVTask task = new MTMVTask(mtmv, relation, context);
+        List<String> result = task.calculateNeedRefreshPartitions(null);
+
+        Assert.assertEquals(Lists.newArrayList(poneName), result);
+    }
+
     @Test
     public void testCalculateNeedRefreshPartitionsSystemNotSyncComplete() 
throws AnalysisException {
         mtmvPartitionUtilStatic.when(() -> 
MTMVPartitionUtil.isMTMVSync(Mockito.nullable(MTMVRefreshContext.class), 
Mockito.nullable(Set.class), Mockito.nullable(Set.class))).thenReturn(false);
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTest.java
index 59d669a3c02..25c62451e43 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVTest.java
@@ -177,6 +177,111 @@ public class MTMVTest {
         Assert.assertTrue(excludedTriggerTables.contains(new 
TableNameInfo(null, null, "t3")));
     }
 
+    @Test
+    public void testAlterMvPropertiesWithExcludedTriggerTablesChange() {
+        Map<String, String> mvProperties = Maps.newHashMap();
+        mvProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t1");
+        MTMV mtmv = new MTMV();
+        mtmv.setMvProperties(mvProperties);
+        MTMVStatus status = new MTMVStatus(MTMVState.NORMAL, null);
+        mtmv.setStatus(status);
+        MTMVRefreshSnapshot refreshSnapshot = new MTMVRefreshSnapshot();
+        refreshSnapshot.getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        mtmv.setRefreshSnapshot(refreshSnapshot);
+
+        long oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        Map<String, String> newProperties = Maps.newHashMap();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"db1.t1");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(MTMVState.NORMAL, mtmv.getStatus().getState());
+        Assert.assertEquals(oldSchemaChangeVersion + 1, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertTrue(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+
+        mtmv.getRefreshSnapshot().getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"internal.db1.t1");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(MTMVState.NORMAL, mtmv.getStatus().getState());
+        Assert.assertEquals(oldSchemaChangeVersion + 1, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertTrue(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+    }
+
+    @Test
+    public void testAlterMvPropertiesWithSameExcludedTriggerTables() {
+        Map<String, String> mvProperties = Maps.newHashMap();
+        mvProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t1,t2");
+        MTMV mtmv = new MTMV();
+        mtmv.setMvProperties(mvProperties);
+        MTMVRefreshSnapshot refreshSnapshot = new MTMVRefreshSnapshot();
+        refreshSnapshot.getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        mtmv.setRefreshSnapshot(refreshSnapshot);
+
+        long oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        Map<String, String> newProperties = Maps.newHashMap();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t2,t1");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(oldSchemaChangeVersion, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertFalse(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+    }
+
+    @Test
+    public void testAlterMvPropertiesWithReducedExcludedTriggerTables() {
+        Map<String, String> mvProperties = Maps.newHashMap();
+        mvProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t1,t2");
+        MTMV mtmv = new MTMV();
+        mtmv.setMvProperties(mvProperties);
+        mtmv.setStatus(new MTMVStatus(MTMVState.NORMAL, null));
+        MTMVRefreshSnapshot refreshSnapshot = new MTMVRefreshSnapshot();
+        refreshSnapshot.getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        mtmv.setRefreshSnapshot(refreshSnapshot);
+
+        long oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        Map<String, String> newProperties = Maps.newHashMap();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t1");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(MTMVState.NORMAL, mtmv.getStatus().getState());
+        Assert.assertEquals(oldSchemaChangeVersion + 1, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertTrue(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+
+        mtmv.getRefreshSnapshot().getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(MTMVState.NORMAL, mtmv.getStatus().getState());
+        Assert.assertEquals(oldSchemaChangeVersion + 1, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertTrue(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+    }
+
+    @Test
+    public void testAlterMvPropertiesWithOtherProperty() {
+        Map<String, String> mvProperties = Maps.newHashMap();
+        mvProperties.put(PropertyAnalyzer.PROPERTIES_EXCLUDED_TRIGGER_TABLES, 
"t1");
+        MTMV mtmv = new MTMV();
+        mtmv.setMvProperties(mvProperties);
+        MTMVRefreshSnapshot refreshSnapshot = new MTMVRefreshSnapshot();
+        refreshSnapshot.getPartitionSnapshots().put("p1", new 
MTMVRefreshPartitionSnapshot());
+        mtmv.setRefreshSnapshot(refreshSnapshot);
+
+        long oldSchemaChangeVersion = mtmv.getSchemaChangeVersion();
+        Map<String, String> newProperties = Maps.newHashMap();
+        newProperties.put(PropertyAnalyzer.PROPERTIES_GRACE_PERIOD, "10");
+
+        mtmv.alterMvProperties(newProperties);
+
+        Assert.assertEquals(oldSchemaChangeVersion, 
mtmv.getSchemaChangeVersion());
+        
Assert.assertFalse(mtmv.getRefreshSnapshot().getPartitionSnapshots().isEmpty());
+    }
+
     @Test
     public void testAlterStatus() {
         MTMV mtmv = new MTMV();
diff --git a/regression-test/data/mtmv_p0/test_create_mtmv_with_view.out 
b/regression-test/data/mtmv_p0/test_create_mtmv_with_view.out
index 90c7a97c096..f083632719a 100644
--- a/regression-test/data/mtmv_p0/test_create_mtmv_with_view.out
+++ b/regression-test/data/mtmv_p0/test_create_mtmv_with_view.out
@@ -22,9 +22,10 @@ COMPLETE
 2      2
 
 -- !trigger_table_not_need_refresh --
-NOT_REFRESH
+COMPLETE
 
 -- !after_trigger_table --
 1      1
 2      2
+3      3
 
diff --git a/regression-test/data/mtmv_p0/test_excluded_trigger_table_mtmv.out 
b/regression-test/data/mtmv_p0/test_excluded_trigger_table_mtmv.out
index 4a2ede2fe66..4c0bc1f0076 100644
--- a/regression-test/data/mtmv_p0/test_excluded_trigger_table_mtmv.out
+++ b/regression-test/data/mtmv_p0/test_excluded_trigger_table_mtmv.out
@@ -4,12 +4,18 @@
 
 -- !true_table --
 1      1
+2      2
 
 -- !true_db_table --
 1      1
+2      2
+3      3
 
 -- !true_ctl_db_table --
 1      1
+2      2
+3      3
+4      4
 
 -- !false_ctl_db_table --
 1      1
diff --git 
a/regression-test/suites/mtmv_p0/test_excluded_trigger_table_mtmv.groovy 
b/regression-test/suites/mtmv_p0/test_excluded_trigger_table_mtmv.groovy
index 6a2264be699..4794470cd3c 100644
--- a/regression-test/suites/mtmv_p0/test_excluded_trigger_table_mtmv.groovy
+++ b/regression-test/suites/mtmv_p0/test_excluded_trigger_table_mtmv.groovy
@@ -65,7 +65,7 @@ suite("test_excluded_trigger_table_mtmv","mtmv") {
         REFRESH MATERIALIZED VIEW ${mvName} AUTO
         """
     waitingMTMVTaskFinishedByMvName(mvName)
-    // should not refresh
+    // should refresh because excluded_trigger_tables changed and refresh 
baseline should be rebuilt
     order_qt_true_table "SELECT * FROM ${mvName}"
 
     sql """
@@ -78,7 +78,7 @@ suite("test_excluded_trigger_table_mtmv","mtmv") {
          REFRESH MATERIALIZED VIEW ${mvName} AUTO
          """
     waitingMTMVTaskFinishedByMvName(mvName)
-     // should not refresh
+     // should refresh because excluded_trigger_tables changed and refresh 
baseline should be rebuilt
     order_qt_true_db_table "SELECT * FROM ${mvName}"
 
     sql """
@@ -91,7 +91,7 @@ suite("test_excluded_trigger_table_mtmv","mtmv") {
          REFRESH MATERIALIZED VIEW ${mvName} AUTO
          """
     waitingMTMVTaskFinishedByMvName(mvName)
-     // should not refresh
+     // should refresh because excluded_trigger_tables changed and refresh 
baseline should be rebuilt
     order_qt_true_ctl_db_table "SELECT * FROM ${mvName}"
 
     sql """


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

Reply via email to