yujun777 commented on code in PR #62606:
URL: https://github.com/apache/doris/pull/62606#discussion_r3915028025


##########
fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java:
##########
@@ -201,108 +298,583 @@ public void run() throws JobException {
             }
             // Every time a task is run, the relation is regenerated because 
baseTables and baseViews may change,
             // such as deleting a table and creating a view with the same name
-            Pair<Set<TableIf>, Set<TableIf>> tablesInPlan = 
MTMVPlanUtil.getBaseTableFromQuery(mtmv.getQuerySql(), ctx);
-            this.relation = 
MTMVPlanUtil.generateMTMVRelation(tablesInPlan.first, tablesInPlan.second);
+            MTMVPlanUtil.QueryAnalysisResult queryAnalysis = 
MTMVPlanUtil.getBaseTableFromQuery(
+                    mtmv.getQuerySql(), ctx);
+            this.relation = 
MTMVPlanUtil.generateMTMVRelation(queryAnalysis.getAllLevelTables(),
+                    queryAnalysis.getOneLevelTables());
             beforeMTMVRefresh();
-            List<TableIf> tableIfs = Lists.newArrayList(tablesInPlan.first);
+            List<TableIf> tableIfs = 
Lists.newArrayList(queryAnalysis.getAllLevelTables());
             tableIfs.sort(Comparator.comparing(TableIf::getId));
 
-            MTMVRefreshContext context;
-            Pair<List<String>, List<PartitionKeyDesc>> syncPartitions = null;
-            // lock table order by id to avoid deadlock
-            MetaLockUtils.readLockTables(tableIfs);
+            // This checks whether an MV in SCHEMA_CHANGE state still matches
+            // its base-table schema and partition definition. It is not part 
of
+            // refresh fallback: incompatible MV definitions must fail 
directly.
+            ensureQueryUsableIfNeeded(ctx, tableIfs);
+            RefreshRequest request = resolveRefreshRequest();
+            validateIvmBaselineBeforePartitionSync(request);
+            List<RefreshAttemptType> attempts = buildAttempts(request, 
queryAnalysis.containsOneRowRelation());
             try {
-                // if mtmv is schema_change, check if column type has changed
-                // If it's not in the schema_change state, the column type 
definitely won't change.
-                if 
(MTMVState.SCHEMA_CHANGE.equals(mtmv.getStatus().getState())) {
-                    MTMVPlanUtil.ensureMTMVQueryUsable(mtmv, ctx);
-                }
-                if (mtmv.getMvPartitionInfo().getPartitionType() != 
MTMVPartitionType.SELF_MANAGE) {
-                    Set<MTMVRelatedTableIf> pctTables = 
mtmv.getMvPartitionInfo().getPctTables();
-                    for (MTMVRelatedTableIf pctTable : pctTables) {
-                        if (!pctTable.isValidRelatedTable()) {
-                            throw new JobException("MTMV " + mtmv.getName() + 
"'s pct table " + pctTable.getName()
-                                    + " is not a valid pct table anymore, stop 
refreshing."
-                                    + " e.g. Table has multiple partition 
columns"
-                                    + " or including not supported transform 
functions.");
+                syncPartitionsIfNeeded(ctx, tableIfs);
+            } catch (PartitionPlanningException e) {
+                throw new JobException(e.getMessage(), e);
+            }
+            MTMVRefreshContext refreshContext = buildRefreshContext(tableIfs);
+            if (handlePendingIvmBaselineRebuild(refreshContext, request, ctx)) 
{
+                return;
+            }
+            boolean disablePartitionRefresh = false;
+            for (RefreshAttemptType attemptType : attempts) {
+                switch (attemptType) {
+                    case IVM:
+                        AttemptResultType ivmResult = 
executeIvmAttempt(refreshContext, request, ctx, tableIfs);
+                        if (ivmResult == AttemptResultType.SUCCESS) {
+                            return;
+                        }
+                        if (ivmResult == 
AttemptResultType.FALLBACK_TO_COMPLETE) {
+                            disablePartitionRefresh = true;
+                        }
+                        break;
+                    case PARTITIONS:
+                        if (disablePartitionRefresh) {
+                            break;
+                        }
+                        if (executePartitionBasedRefresh(refreshContext, 
request, ctx)) {
+                            return;
                         }
+                        break;
+                    case COMPLETE:
+                        executeCompleteAttempt(refreshContext, ctx);
+                        return;
+                    default:
+                        throw new JobException("Unsupported refresh attempt 
type: " + attemptType);
+                }
+            }
+            throw new JobException("No refresh attempt succeeded for mv=" + 
mtmv.getName());
+        } catch (Throwable e) {
+            if (getStatus() == TaskStatus.RUNNING) {
+                LOG.warn("run task failed, mvName: {}, taskId: {}",
+                        mtmv.getName(), getTaskId(), e);
+                throw new JobException(e.getMessage(), e);
+            } else {
+                // if status is not `RUNNING`,maybe the task was canceled, 
therefore, it is a normal situation
+                LOG.info("task [{}] interruption running, because status is 
[{}]", getTaskId(), getStatus());
+            }
+        }
+    }
+
+    private void ensureQueryUsableIfNeeded(ConnectContext ctx, List<TableIf> 
tableIfs)
+            throws JobException, AnalysisException {
+        MetaLockUtils.readLockTables(tableIfs);
+        try {
+            if (MTMVState.SCHEMA_CHANGE.equals(mtmv.getStatus().getState())) {
+                MTMVPlanUtil.ensureMTMVQueryUsable(mtmv, ctx);
+            }
+        } finally {
+            MetaLockUtils.readUnlockTables(tableIfs);
+        }
+    }
+
+    private void syncPartitionsIfNeeded(ConnectContext ctx, List<TableIf> 
tableIfs)
+            throws JobException, AnalysisException, DdlException, 
PartitionPlanningException {
+        if (isSkipPartitionSyncDebugPointEnabled()) {
+            LOG.info("Skip MTMV partition synchronization for debug point, 
mv={}, taskId={}",
+                    mtmv.getName(), getTaskId());
+            return;
+        }
+        Pair<List<String>, List<PartitionKeyDesc>> syncPartitions = null;
+        // lock table order by id to avoid deadlock
+        MetaLockUtils.readLockTables(tableIfs);
+        try {
+            if (mtmv.getMvPartitionInfo().getPartitionType() != 
MTMVPartitionType.SELF_MANAGE) {
+                Set<MTMVRelatedTableIf> pctTables = 
mtmv.getMvPartitionInfo().getPctTables();
+                for (MTMVRelatedTableIf pctTable : pctTables) {
+                    if (!pctTable.isValidRelatedTable()) {
+                        throw new PartitionPlanningException("MTMV " + 
mtmv.getName()
+                                + "'s pct table " + pctTable.getName()
+                                + " is not a valid pct table anymore, stop 
refreshing."
+                                + " e.g. Table has multiple partition columns"
+                                + " or including not supported transform 
functions.");
                     }
+                }
+                try {
                     syncPartitions = MTMVPartitionUtil.alignMvPartition(mtmv);
+                } catch (Exception e) {
+                    throw new PartitionPlanningException(e.getMessage(), e);
                 }
-            } finally {
-                MetaLockUtils.readUnlockTables(tableIfs);
             }
-            if (syncPartitions != null) {
-                for (String pName : syncPartitions.first) {
-                    MTMVPartitionUtil.dropPartition(mtmv, pName);
+        } finally {
+            MetaLockUtils.readUnlockTables(tableIfs);
+        }
+        if (syncPartitions != null) {
+            for (String pName : syncPartitions.first) {
+                MTMVPartitionUtil.dropPartition(mtmv, pName);
+            }
+            for (PartitionKeyDesc partitionKeyDesc : syncPartitions.second) {
+                MTMVPartitionUtil.addPartition(mtmv, partitionKeyDesc);
+            }
+        }
+    }
+
+    private boolean isSkipPartitionSyncDebugPointEnabled() {
+        String targetMvName = DebugPointUtil.getDebugParamOrDefault(
+                DEBUG_POINT_SKIP_PARTITION_SYNC_FILTER, "mv_name", "");
+        return mtmv.getName().equals(targetMvName)
+                && DebugPointUtil.isEnable(DEBUG_POINT_SKIP_PARTITION_SYNC);
+    }
+
+    private RefreshRequest resolveRefreshRequest() throws JobException {
+        if (taskContext.useMvDefaultRefreshPolicy()) {
+            // Scheduled/on-commit/system tasks use the policy persisted on the
+            // MV, not the default AUTO value of a newly created task context.
+            RefreshMethod refreshMethod = 
mtmv.getRefreshInfo().getRefreshMethod();
+            if (refreshMethod == null) {
+                throw new JobException("MTMV " + mtmv.getName()
+                        + " has unknown refresh method, please refresh or 
recreate it.");
+            }
+            return new 
RefreshRequest(RefreshMode.valueOf(refreshMethod.name()),
+                    mtmv.getRefreshInfo().allowFallback(), 
Lists.newArrayList(), false);
+        }
+        if (!CollectionUtils.isEmpty(taskContext.getPartitions())) {
+            // A partitionSpec is an exact manual request. It never falls back 
to
+            // COMPLETE because that would refresh more data than the user 
asked.
+            return new RefreshRequest(RefreshMode.PARTITIONS, false, 
taskContext.getPartitions(), true);
+        }
+        return new RefreshRequest(taskContext.getRefreshMode(), 
taskContext.allowFallback(),
+                Lists.newArrayList(), false);
+    }
+
+    private List<RefreshAttemptType> buildAttempts(RefreshRequest request, 
boolean containsOneRowRelation) {
+        if (shouldUseCompleteForInitialIvmRefresh(containsOneRowRelation)) {
+            return Lists.newArrayList(RefreshAttemptType.COMPLETE);
+        }
+        List<RefreshAttemptType> attempts = Lists.newArrayList();
+        switch (request.refreshMode) {
+            case AUTO:
+                // ALTER excluded_trigger_tables clears a successful baseline 
without changing the MV state.
+                if (!mtmv.isIvm() && !mtmv.hasRefreshSnapshot()
+                        && mtmv.getStatus().getState() == MTMVState.NORMAL
+                        && mtmv.getStatus().getRefreshState() == 
MTMVRefreshState.SUCCESS) {
+                    attempts.add(RefreshAttemptType.COMPLETE);
+                    break;
+                }
+                if (mtmv.isIvm()) {
+                    attempts.add(RefreshAttemptType.IVM);
                 }
-                for (PartitionKeyDesc partitionKeyDesc : 
syncPartitions.second) {
-                    MTMVPartitionUtil.addPartition(mtmv, partitionKeyDesc);
+                // AUTO always ends with COMPLETE. For an MV defined REFRESH 
COMPLETE,
+                // skip the PARTITIONS attempt: its sync check treats base 
tables that are
+                // not MTMVRelatedTableIf (external tables, views) as always 
synchronous,
+                // so the refresh would be skipped forever after the first 
build.
+                if (mtmv.getRefreshInfo().getRefreshMethod() != 
RefreshMethod.COMPLETE) {
+                    attempts.add(RefreshAttemptType.PARTITIONS);
+                } else {
+                    LOG.info("AUTO refresh of COMPLETE-method mv={} skips 
partition-sync check "
+                            + "and refreshes all directly, taskId={}", 
mtmv.getName(), super.getTaskId());
                 }
+                attempts.add(RefreshAttemptType.COMPLETE);
+                break;
+            case INCREMENTAL:
+                attempts.add(RefreshAttemptType.IVM);
+                if (request.allowFallback) {
+                    attempts.add(RefreshAttemptType.PARTITIONS);
+                    attempts.add(RefreshAttemptType.COMPLETE);
+                }
+                break;
+            case PARTITIONS:
+                attempts.add(RefreshAttemptType.PARTITIONS);
+                if (!request.explicitPartitions && request.allowFallback) {
+                    attempts.add(RefreshAttemptType.COMPLETE);
+                }
+                break;
+            case COMPLETE:
+                attempts.add(RefreshAttemptType.COMPLETE);
+                break;
+            default:
+                throw new IllegalStateException("Unsupported refresh mode: " + 
request.refreshMode);
+        }
+        return attempts;
+    }
+
+    private boolean shouldUseCompleteForInitialIvmRefresh(boolean 
containsOneRowRelation) {
+        return mtmv.isIvm() && !mtmv.hasRefreshSnapshot()
+                && (taskContext.getTriggerMode() != MTMVTaskTriggerMode.MANUAL 
|| containsOneRowRelation);

Review Comment:
   Fixed in 10295e68806. `shouldUseCompleteForInitialIvmRefresh` now forces the 
first refresh to COMPLETE whenever `excluded_trigger_tables` is set (previously 
only non-manual triggers or one-row relations did): an excluded source has no 
delta stream, so an INCREMENTAL first refresh could never materialize its 
pre-existing rows before a baseline was recorded. Covered by the updated 
`test_ivm_excluded_trigger_table` (first INCREMENTAL refresh with excluded 
sources must contain the excluded table's rows).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to