mirror of
https://github.com/apache/doris.git
synced 2026-11-04 07:31:17 +00:00
[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
This commit is contained in:
1 parent
bdb90856e6
commit
3763f09897
2 files changed
+80
No files matched your search
+11
@@ -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()
|
||||
|
||||
+69
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user