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

Gabriel39 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 3763f098979 [fix](fe) Skip table snapshot resolution for external 
views (#69836)
3763f098979 is described below

commit 3763f0989794c14906e56757eb975d314918697a
Author: daidai <[email protected]>
AuthorDate: Tue Nov 3 10:26:41 2026 +0800

    [fix](fe) Skip table snapshot resolution for external views (#69836)
    
    ### What problem does this PR solve?
    
    External views currently resolve a table handle when loading the
    statement snapshot, before their SQL is expanded. Skip that table-only
    path for known views; their base tables retain normal snapshot pinning.
    
    Reject unsupported view scan parameters such as `@branch` and `@tag`
    instead of silently ignoring the requested selector.
    
    ### Release note
    
    Avoid table metadata requests for external views during snapshot
    binding, and reject unsupported view scan parameters.
    
    ### Check List (For Author)
    
    For this update: Checkstyle passed for both changed Java files.
    Selector-rejection unit tests were added but not run locally.
    
    - Test
        - [x] Regression test
        - [x] Unit Test
        - [x] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason
    
    - Behavior changed:
        - [ ] No.
        - [x] Yes.
    
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 .../mvcc/PluginDrivenMvccExternalTable.java        | 11 ++++
 .../mvcc/PluginDrivenMvccExternalTableTest.java    | 69 ++++++++++++++++++++++
 2 files changed, 80 insertions(+)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java
index f12616cfd76..84fbdc2f58e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java
@@ -505,6 +505,17 @@ public class PluginDrivenMvccExternalTable extends 
PluginDrivenExternalTable
             Optional<TableSnapshot> tableSnapshot,
             Optional<TableScanParams> scanParams,
             Optional<MvccSnapshot> latestSnapshotFence) {
+        // Views have no table snapshot. BindRelation expands their SQL and 
pins the base tables;
+        // resolving a table handle here would load the view name through the 
table API.
+        if (isView()) {
+            // View expansion cannot apply relation scan parameters to the 
base tables.
+            if (scanParams.isPresent()) {
+                throw new UnsupportedOperationException(
+                        "view not supported with scan params: @" + 
scanParams.get().getParamType());
+            }
+            return new PluginDrivenMvccSnapshot(emptySnapshot(),
+                    Collections.emptyMap(), Collections.emptyMap());
+        }
         if (!tableSnapshot.isPresent() && !scanParams.isPresent()) {
             // B5a implicit query-begin (latest) pin.
             return latestSnapshotFence.isPresent()
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTableTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTableTest.java
index f2f9fc645f2..15a35c77df0 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTableTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTableTest.java
@@ -901,6 +901,75 @@ public class PluginDrivenMvccExternalTableTest {
 
     // ==================== loadSnapshot: B5a latest materialize 
====================
 
+    @Test
+    public void testViewSnapshotDoesNotUseTableMetadata() {
+        Fixture f = Fixture.partitioned();
+        PluginDrivenMvccExternalTable view = Mockito.spy(f.table);
+        Mockito.doReturn(true).when(view).isView();
+        // A catalog may support the view endpoint but reject table loads for 
this name. Query binding
+        // must not depend on whether the unrelated table request returns 404 
or another error.
+        Mockito.when(f.metadata.getTableHandle(f.session, "REMOTE_DB", 
"REMOTE_TBL"))
+                .thenThrow(new IllegalStateException("table endpoint is 
unavailable for this view"));
+        PluginDrivenMvccSnapshot fence = new PluginDrivenMvccSnapshot(
+                
ConnectorMvccSnapshot.builder().snapshotId(PINNED_SNAPSHOT_ID).build(),
+                Collections.emptyMap(), Collections.emptyMap());
+
+        List<MvccSnapshot> snapshots = Arrays.asList(
+                view.loadSnapshot(Optional.empty(), Optional.empty()),
+                view.loadSnapshot(Optional.empty(), Optional.empty(), 
Optional.empty()),
+                view.loadSnapshot(Optional.empty(), Optional.empty(), 
Optional.of(fence)));
+
+        for (MvccSnapshot snapshot : snapshots) {
+            PluginDrivenMvccSnapshot pin = 
Assertions.assertInstanceOf(PluginDrivenMvccSnapshot.class, snapshot);
+            Assertions.assertEquals(-1L, 
pin.getConnectorSnapshot().getSnapshotId());
+            Assertions.assertNull(pin.getPinnedSchema());
+            Assertions.assertTrue(pin.getNameToPartitionItem().isEmpty());
+            Assertions.assertTrue(pin.getNameToLastModifiedMillis().isEmpty());
+            Assertions.assertSame(f.latestCacheValue, 
view.getSchemaCacheValue(Optional.of(pin)).get());
+        }
+        Mockito.verifyNoInteractions(f.metadata);
+    }
+
+    @Test
+    public void testViewSnapshotRejectsScanParamsWithoutTableMetadata() {
+        Fixture f = Fixture.partitioned();
+        PluginDrivenMvccExternalTable view = Mockito.spy(f.table);
+        Mockito.doReturn(true).when(view).isView();
+        PluginDrivenMvccSnapshot fence = new PluginDrivenMvccSnapshot(
+                
ConnectorMvccSnapshot.builder().snapshotId(PINNED_SNAPSHOT_ID).build(),
+                Collections.emptyMap(), Collections.emptyMap());
+        Map<String, String> incrementalParams = new HashMap<>();
+        incrementalParams.put("startSnapshotId", "1");
+        incrementalParams.put("endSnapshotId", "5");
+        List<TableScanParams> selectors = Arrays.asList(
+                new TableScanParams(TableScanParams.BRANCH,
+                        Collections.emptyMap(), 
Collections.singletonList("b1")),
+                new TableScanParams(TableScanParams.BRANCH,
+                        Collections.singletonMap(TableScanParams.PARAMS_NAME, 
"b1"), Collections.emptyList()),
+                new TableScanParams(TableScanParams.TAG,
+                        Collections.emptyMap(), 
Collections.singletonList("t1")),
+                new TableScanParams(TableScanParams.TAG,
+                        Collections.singletonMap(TableScanParams.PARAMS_NAME, 
"t1"), Collections.emptyList()),
+                new TableScanParams(TableScanParams.INCREMENTAL_READ, 
incrementalParams, Collections.emptyList()),
+                new TableScanParams(TableScanParams.OPTIONS,
+                        Collections.singletonMap("scan.snapshot-id", "7"), 
Collections.emptyList()));
+
+        for (TableScanParams selector : selectors) {
+            List<UnsupportedOperationException> errors = Arrays.asList(
+                    
Assertions.assertThrows(UnsupportedOperationException.class,
+                            () -> view.loadSnapshot(Optional.empty(), 
Optional.of(selector))),
+                    
Assertions.assertThrows(UnsupportedOperationException.class,
+                            () -> view.loadSnapshot(Optional.empty(), 
Optional.of(selector), Optional.empty())),
+                    
Assertions.assertThrows(UnsupportedOperationException.class,
+                            () -> view.loadSnapshot(Optional.empty(), 
Optional.of(selector), Optional.of(fence))));
+            for (UnsupportedOperationException error : errors) {
+                Assertions.assertEquals("view not supported with scan params: 
@" + selector.getParamType(),
+                        error.getMessage());
+            }
+        }
+        Mockito.verifyNoInteractions(f.metadata);
+    }
+
     @Test
     public void testLoadSnapshotEmptyMaterializes() {
         Fixture f = Fixture.partitioned();


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

Reply via email to