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 2e89f3a81cb [fix](mv) Prevent snapshot reads from using current MVs
(#67700)
2e89f3a81cb is described below
commit 2e89f3a81cbf0effe06491a383109ab36ec72923
Author: morrySnow <[email protected]>
AuthorDate: Wed Sep 9 16:19:12 2026 +0800
[fix](mv) Prevent snapshot reads from using current MVs (#67700)
## Problem
An external-table query that explicitly reads an older snapshot can
still enter materialized view rewrite. The candidate materialized view
represents the table state at its refresh snapshot, so using a current
materialized view for a historical query can silently return data from
the wrong point in time.
## Root cause
`LogicalFileScan` stores standard `FOR TIME AS OF` and `FOR VERSION AS
OF` clauses in `tableSnapshot`. The materialized-view eligibility
checker rejected scans with table samples or scan parameters, but did
not inspect `tableSnapshot`. As a result, the historical scan was
treated like an ordinary latest-snapshot scan.
## How to reproduce
1. Create an Iceberg table and insert an initial row, then record that
snapshot ID.
2. Insert newer data and refresh a materialized view over the table at
the current snapshot.
3. Enable materialized-view rewrite and query the Iceberg table with
`FOR VERSION AS OF <old_snapshot_id>` (the same issue applies to `FOR
TIME AS OF`).
4. Before this change, the historical query can be considered eligible
for rewrite by the current-snapshot materialized view, producing current
rather than historical results.
The same condition can be reproduced directly in the optimizer by
building a `LogicalFileScan` with a non-empty `tableSnapshot`: the
table-query-operator checker previously returned false.
## Fix
Treat a non-empty `LogicalFileScan.tableSnapshot` as a table-level query
operator, alongside table samples and scan parameters. This
conservatively prevents materialized-view rewrite until the optimizer
can prove that the query snapshot and materialized-view refresh snapshot
are semantically equivalent.
Add a unit test that constructs a file scan with a version snapshot and
verifies that the checker rejects it from ordinary rewrite eligibility.
---
.../nereids/rules/exploration/mv/MaterializedViewUtils.java | 6 ++++--
.../rules/exploration/mv/MaterializedViewUtilsTest.java | 12 ++++++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
index 4b6221cd5a9..9ccf8de2114 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtils.java
@@ -630,9 +630,11 @@ public class MaterializedViewUtils {
public Boolean visitLogicalRelation(LogicalRelation relation, Void
context) {
if (relation instanceof LogicalFileScan) {
LogicalFileScan fileScan = (LogicalFileScan) relation;
- // Relation scan parameters can select data different from the
MV refresh input.
+ // Relation scan operators can select data different from the
MV refresh input.
// Treat them as query operators until rewrite can prove
equivalent semantics.
- if (fileScan.getTableSample().isPresent() ||
fileScan.getScanParams().isPresent()) {
+ if (fileScan.getTableSample().isPresent()
+ || fileScan.getScanParams().isPresent()
+ || fileScan.getTableSnapshot().isPresent()) {
return true;
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtilsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtilsTest.java
index db357bed441..7f235171043 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtilsTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/mv/MaterializedViewUtilsTest.java
@@ -18,6 +18,7 @@
package org.apache.doris.nereids.rules.exploration.mv;
import org.apache.doris.analysis.TableScanParams;
+import org.apache.doris.analysis.TableSnapshot;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.mtmv.BaseTableInfo;
@@ -1016,6 +1017,17 @@ public class MaterializedViewUtilsTest extends
TestWithFeService {
.visitLogicalRelation(fileScan, null));
}
+ @Test
+ public void containTableQueryOperatorWithTableSnapshotTest() {
+ LogicalFileScan fileScan = Mockito.mock(LogicalFileScan.class);
+ Mockito.when(fileScan.getTableSample()).thenReturn(Optional.empty());
+ Mockito.when(fileScan.getScanParams()).thenReturn(Optional.empty());
+
Mockito.when(fileScan.getTableSnapshot()).thenReturn(Optional.of(TableSnapshot.versionOf("1")));
+
+
Assertions.assertTrue(MaterializedViewUtils.TableQueryOperatorChecker.INSTANCE
+ .visitLogicalRelation(fileScan, null));
+ }
+
@Test
public void getRelatedTableInfoWhenMultiPartitionExprs() {
PlanChecker.from(connectContext)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]