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 e056f1cddc5 [fix](mtmv) Preserve origin statement for internal refresh
(#68269)
e056f1cddc5 is described below
commit e056f1cddc5531526380a491b31e0a91a5901173
Author: morrySnow <[email protected]>
AuthorDate: Thu Sep 24 15:08:39 2026 +0800
[fix](mtmv) Preserve origin statement for internal refresh (#68269)
### What problem does this PR solve?
Problem Summary:
Internally constructed MTMV refresh commands could lose their origin
statement. Complete refreshes created a `StatementContext` without the
MV query, while `StmtExecutor` copied a null `LogicalPlanAdapter` origin
back into contexts that already had one. Complete and partition
refreshes now use the MV query as their origin and pass the same origin
to the adapter.
Incremental refresh and incremental dry run had a related parsing order
bug: an MV definition with a `SET_VAR` hint was parsed before its new
`StatementContext` was installed on the internal `ConnectContext`. Hint
parsing reads that context and could fail with a null dereference. Both
paths now install the origin-bearing context before parsing the MV
query. The existing dry-run regression case uses a hinted MV definition
to cover both refresh paths.
### Release note
Fix internal materialized view refresh planning to retain its
originating SQL statement, and allow incremental refresh and dry run of
MV queries containing `SET_VAR` hints.
---
.../apache/doris/job/extensions/mtmv/MTMVTask.java | 8 ++++---
.../java/org/apache/doris/mtmv/MTMVPlanUtil.java | 7 +++++-
.../doris/mtmv/ivm/IvmIncrRefreshManager.java | 2 ++
.../trees/plans/commands/RefreshMTMVCommand.java | 3 +++
.../java/org/apache/doris/mtmv/MTMVTaskTest.java | 5 ++++
.../doris/mtmv/ivm/IvmIncrRefreshManagerTest.java | 27 ++++++++++++++++++++++
.../plans/commands/RefreshMTMVCommandTest.java | 18 +++++++++++++++
.../commands/UpdateMvByPartitionCommandTest.java | 9 +++++++-
.../mtmv_p0/ivm/test_ivm_refresh_dry_run.groovy | 2 +-
9 files changed, 75 insertions(+), 6 deletions(-)
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 f0445240602..0bf826434b4 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
@@ -78,6 +78,7 @@ import
org.apache.doris.nereids.trees.plans.commands.CreateMTMVCommand;
import
org.apache.doris.nereids.trees.plans.commands.UpdateMvByPartitionCommand;
import
org.apache.doris.nereids.trees.plans.commands.info.RefreshMTMVInfo.RefreshMode;
import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.OriginStatement;
import org.apache.doris.qe.QeProcessorImpl;
import org.apache.doris.qe.StmtExecutor;
import org.apache.doris.rpc.RpcException;
@@ -1157,10 +1158,11 @@ public class MTMVTask extends AbstractTask {
Map<TableIf, String> tableWithPartKey,
Optional<IvmRewriteContext> rewriteContext, RefreshMode
refreshMode)
throws Exception {
- // Create MTMV context first so that new StatementContext() captures
the
- // correct thread-local ConnectContext (with MTMV disabled rules,
etc.).
+ // Create the MTMV context before parsing the MV definition SQL so
SET_VAR hints
+ // resolve against the internal session (with MTMV disabled rules,
etc.).
ConnectContext mtmvCtx = MTMVPlanUtil.createMTMVContext(mtmv,
MTMVPlanUtil.DISABLE_RULES_WHEN_RUN_MTMV_TASK);
- StatementContext statementContext = new StatementContext();
+ StatementContext statementContext = new StatementContext(
+ mtmvCtx, new OriginStatement(mtmv.getQuerySql(), 0));
// Install the StatementContext on the ConnectContext before parsing
// the MV definition SQL. UpdateMvByPartitionCommand.from() calls
// NereidsParser.parseSingle() which, for SQL containing SET_VAR hints,
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
index b937c93065a..e9ee4595a0f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPlanUtil.java
@@ -172,6 +172,8 @@ public class MTMVPlanUtil {
* executing {@link StmtExecutor} through {@code executorConsumer} before
the command
* runs and clearing it (with {@code null}) after the command finishes, so
task
* cancellation can interrupt the running statement.
+ *
+ * <p>The supplied statement context must contain the originating SQL
statement.
*/
public static void executeCommand(ConnectContext ctx, Command command,
StatementContext stmtCtx, @Nullable String auditStmt,
@@ -180,7 +182,10 @@ public class MTMVPlanUtil {
ctx.getState().setNereids(true);
ctx.getSessionVariable().setEnableMaterializedViewRewrite(false);
ctx.getSessionVariable().setEnableDmlMaterializedViewRewrite(false);
- StmtExecutor executor = new StmtExecutor(ctx, new
LogicalPlanAdapter(command, stmtCtx));
+ LogicalPlanAdapter adapter = new LogicalPlanAdapter(command, stmtCtx);
+
adapter.setOrigStmt(Preconditions.checkNotNull(stmtCtx.getOriginStatement(),
+ "MTMV command origin statement must not be null"));
+ StmtExecutor executor = new StmtExecutor(ctx, adapter);
ctx.setExecutor(executor);
ctx.setQueryId(AbstractTask.generateQueryId());
if (executorConsumer != null) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManager.java
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManager.java
index 4559fd54937..cfbdff60d18 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManager.java
@@ -97,6 +97,8 @@ public class IvmIncrRefreshManager {
MTMV mtmv = context.getMtmv();
StatementContext statementContext = new StatementContext(
context.getConnectContext(), new
OriginStatement(mtmv.getQuerySql(), 0));
+ // SET_VAR hints are applied while parsing the MV query, before
executeCommand runs.
+ context.getConnectContext().setStatementContext(statementContext);
// The delta may only read the base partitions the MV's partition
definition keeps. A base
// partition outside that set, expired by partition_sync_limit, would
otherwise still be
// read through the delta and the join-opposite snapshot, and its rows
would have no MV
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommand.java
index 55f0e42e735..16507e80bd3 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommand.java
@@ -152,6 +152,9 @@ public class RefreshMTMVCommand extends Command implements
Forward, Explainable
stmtCtx.setIvmRewriteContext(Optional.of(IvmRewriteContext.incrementalDryRun(mtmv,
dryRunLimit)));
// Excluded trigger tables must not be validated for binlog / key-type
support.
stmtCtx.setExcludedTriggerTables(mtmv.getExcludedTriggerTables());
+ // The MV query is parsed before the internal executor is created.
SET_VAR hints
+ // need this context already installed on the internal session during
parsing.
+ internalCtx.setStatementContext(stmtCtx);
return stmtCtx;
}
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 47a0d2403e4..5a7aecfffb6 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
@@ -558,6 +558,7 @@ public class MTMVTaskTest {
Mockito.when(mtmv.getExcludedTriggerTables()).thenReturn(excludedTriggerTables);
Mockito.when(mtmv.isIvm()).thenReturn(true);
Mockito.when(mtmv.getName()).thenReturn("test_mv");
+ Mockito.when(mtmv.getQuerySql()).thenReturn("select k1 from
test_db.base_table");
Mockito.when(mtmv.getDatabase()).thenReturn(null);
Mockito.when(mtmvPartitionInfo.getPartitionType()).thenReturn(MTMVPartitionType.FOLLOW_BASE_TABLE);
@@ -583,6 +584,8 @@ public class MTMVTaskTest {
public UpdateMvByPartitionCommand
answer(InvocationOnMock invocation) {
StatementContext statementContext =
invocation.getArgument(3);
Assertions.assertEquals(excludedTriggerTables,
statementContext.getExcludedTriggerTables());
+ Assertions.assertEquals("select k1 from
test_db.base_table",
+
statementContext.getOriginStatement().originStmt);
return command;
}
});
@@ -593,6 +596,8 @@ public class MTMVTaskTest {
public Void answer(InvocationOnMock invocation) {
StatementContext statementContext =
invocation.getArgument(2);
Assertions.assertEquals(excludedTriggerTables,
statementContext.getExcludedTriggerTables());
+ Assertions.assertEquals("select k1 from
test_db.base_table",
+
statementContext.getOriginStatement().originStmt);
return null;
}
});
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManagerTest.java
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManagerTest.java
index 3a4a4519e12..59a57d660a0 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManagerTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/ivm/IvmIncrRefreshManagerTest.java
@@ -90,6 +90,33 @@ public class IvmIncrRefreshManagerTest {
Assertions.assertEquals(excluded,
captured.get().getExcludedTriggerTables());
}
+ @Test
+ public void testIncrementalRefreshParsesSetVarWithItsStatementContext()
throws Exception {
+ MTMV mtmv = mockMtmv();
+ Mockito.when(mtmv.getQuerySql()).thenReturn("SELECT /*+
SET_VAR(query_timeout=10) */ 1 AS k1");
+ Mockito.when(mtmv.getInsertedColumnNames()).thenReturn(List.of("k1"));
+ ConnectContext connectContext = new ConnectContext();
+ IvmIncrRefreshContext context = new IvmIncrRefreshContext(mtmv,
connectContext, "audit",
+ queryId -> { }, null);
+ connectContext.setThreadLocalInfo();
+ try (MockedStatic<MTMVPlanUtil> mockedUtil =
Mockito.mockStatic(MTMVPlanUtil.class)) {
+ mockedUtil.when(() -> MTMVPlanUtil.executeCommand(
+ Mockito.<ConnectContext>any(), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenAnswer(inv -> {
+ StatementContext stmtCtx = inv.getArgument(2);
+ Assertions.assertSame(stmtCtx,
connectContext.getStatementContext());
+ Assertions.assertEquals(mtmv.getQuerySql(),
stmtCtx.getOriginStatement().originStmt);
+ Assertions.assertEquals(10,
connectContext.getSessionVariable().getQueryTimeoutS());
+ return null;
+ });
+ new IvmIncrRefreshManager().executeInternalRefresh(context);
+ mockedUtil.verify(() -> MTMVPlanUtil.executeCommand(
+ Mockito.eq(connectContext), Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any()));
+ } finally {
+ ConnectContext.remove();
+ }
+ }
+
@Test
public void testManagerReturnsSuccessForEmptyBundles() throws Exception {
MTMV mtmv = mockMtmv();
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommandTest.java
index 689d7fb0dad..81becc8b5a9 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/RefreshMTMVCommandTest.java
@@ -172,6 +172,24 @@ public class RefreshMTMVCommandTest {
Assertions.assertEquals(excluded, stmtCtx.getExcludedTriggerTables());
}
+ @Test
+ public void testIncrementalDryRunParsesSetVarWithItsStatementContext()
throws Exception {
+ RefreshMTMVInfo info = extractRefreshInfo("REFRESH MATERIALIZED VIEW
db1.mv1 INCREMENTAL");
+ TestRefreshMTMVCommand command = new TestRefreshMTMVCommand(info,
true);
+ MTMV mtmv = Mockito.mock(MTMV.class);
+ Mockito.when(mtmv.getQuerySql()).thenReturn("SELECT /*+
SET_VAR(query_timeout=10) */ 1 AS k1");
+ ConnectContext internalCtx = new ConnectContext();
+ internalCtx.setThreadLocalInfo();
+ try {
+ StatementContext stmtCtx =
command.createDryRunStatementContext(mtmv, internalCtx);
+ new IvmIncrRefreshManager().buildQueryPlan(mtmv);
+ Assertions.assertSame(stmtCtx, internalCtx.getStatementContext());
+ Assertions.assertEquals(10,
internalCtx.getSessionVariable().getQueryTimeoutS());
+ } finally {
+ ConnectContext.remove();
+ }
+ }
+
@Test
public void testIncrementalExplainCarriesExcludedTriggerTables() throws
Exception {
RefreshMTMVInfo info = extractRefreshInfo("REFRESH MATERIALIZED VIEW
db1.mv1 INCREMENTAL");
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/UpdateMvByPartitionCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/UpdateMvByPartitionCommandTest.java
index 0eb933b45a4..4228865cfa4 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/UpdateMvByPartitionCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/UpdateMvByPartitionCommandTest.java
@@ -46,6 +46,7 @@ import org.apache.doris.nereids.util.PlanChecker;
import org.apache.doris.planner.ExchangeNode;
import org.apache.doris.planner.OlapTableSink;
import org.apache.doris.planner.PlanFragment;
+import org.apache.doris.qe.OriginStatement;
import org.apache.doris.qe.StmtExecutor;
import org.apache.doris.thrift.TPartitionType;
import org.apache.doris.utframe.TestWithFeService;
@@ -241,6 +242,7 @@ class UpdateMvByPartitionCommandTest extends
TestWithFeService {
void testRunRefreshCommandExecutesIncrementalMtmv() throws Exception {
MTMV mtmv = getMtmv("ivm_mv");
StatementContext statementContext = createStatementCtx("refresh
materialized view test.ivm_mv");
+ OriginStatement originStatement =
statementContext.getOriginStatement();
statementContext.setIvmRewriteContext(Optional.of(IvmRewriteContext.full(mtmv)));
UpdateMvByPartitionCommand command = newRefreshCommand(mtmv);
AtomicReference<StmtExecutor> executorRef = new AtomicReference<>();
@@ -260,12 +262,15 @@ class UpdateMvByPartitionCommandTest extends
TestWithFeService {
executor.getContext().getStatementContext().getIvmRewriteContext().orElseThrow().getMode());
Assertions.assertSame(executor.getContext(),
statementContext.getConnectContext());
Assertions.assertSame(statementContext,
executor.getContext().getStatementContext());
+ Assertions.assertSame(originStatement,
statementContext.getOriginStatement());
+ Assertions.assertSame(originStatement,
executor.getParsedStmt().getOrigStmt());
}
@Test
void testExecuteCommandRebindsTaskStatementContextToExecutionContext()
throws Exception {
MTMV mtmv = getMtmv("ivm_mv");
- StatementContext statementContext = new StatementContext();
+ StatementContext statementContext = createStatementCtx("refresh
materialized view test.ivm_mv");
+ OriginStatement originStatement =
statementContext.getOriginStatement();
statementContext.setIvmRewriteContext(Optional.of(IvmRewriteContext.full(mtmv)));
UpdateMvByPartitionCommand command = UpdateMvByPartitionCommand.from(
mtmv, Sets.newHashSet(), ImmutableMap.of(), statementContext);
@@ -281,6 +286,8 @@ class UpdateMvByPartitionCommandTest extends
TestWithFeService {
Assertions.assertSame(executor.getContext(),
statementContext.getConnectContext());
Assertions.assertSame(statementContext,
executor.getContext().getStatementContext());
+ Assertions.assertSame(originStatement,
statementContext.getOriginStatement());
+ Assertions.assertSame(originStatement,
executor.getParsedStmt().getOrigStmt());
}
@Test
diff --git a/regression-test/suites/mtmv_p0/ivm/test_ivm_refresh_dry_run.groovy
b/regression-test/suites/mtmv_p0/ivm/test_ivm_refresh_dry_run.groovy
index f2583e14e8d..073d400a1b5 100644
--- a/regression-test/suites/mtmv_p0/ivm/test_ivm_refresh_dry_run.groovy
+++ b/regression-test/suites/mtmv_p0/ivm/test_ivm_refresh_dry_run.groovy
@@ -48,7 +48,7 @@ suite("test_ivm_refresh_dry_run") {
BUILD DEFERRED REFRESH INCREMENTAL ON MANUAL
DISTRIBUTED BY RANDOM BUCKETS 2
PROPERTIES ('replication_num' = '1')
- AS SELECT k1, COUNT(*) AS cnt, SUM(v1) AS sum_v1
+ AS SELECT /*+ SET_VAR(query_timeout=180) */ k1, COUNT(*) AS cnt,
SUM(v1) AS sum_v1
FROM test_ivm_refresh_dry_run_base
GROUP BY k1
"""
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]