diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java index f08f3ec899af9c..58e69e03ff62ea 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java @@ -190,7 +190,7 @@ public List listTableNames(String dbName) { // IcebergMetadataOps handles listTableNames and listViewNames separately. // listTableNames should only focus on the table type, // but in reality, Iceberg's return includes views. Therefore, we added a filter to exclude views. - if (catalog instanceof ViewCatalog) { + if (isViewCatalogEnabled()) { views = ((ViewCatalog) catalog).listViews(getNamespace(dbName)) .stream().map(TableIdentifier::name).collect(Collectors.toList()); } else { @@ -1126,7 +1126,7 @@ public Table loadTable(String dbName, String tblName) { @Override public boolean viewExists(String remoteDbName, String remoteViewName) { - if (!(catalog instanceof ViewCatalog)) { + if (!isViewCatalogEnabled()) { return false; } try { @@ -1140,7 +1140,7 @@ public boolean viewExists(String remoteDbName, String remoteViewName) { @Override public Object loadView(String dbName, String tblName) { - if (!(catalog instanceof ViewCatalog)) { + if (!isViewCatalogEnabled()) { return null; } try { @@ -1154,7 +1154,7 @@ public Object loadView(String dbName, String tblName) { @Override public List listViewNames(String db) { - if (!(catalog instanceof ViewCatalog)) { + if (!isViewCatalogEnabled()) { return Collections.emptyList(); } try { @@ -1192,12 +1192,25 @@ private Namespace getNamespace() { return externalCatalogName.map(Namespace::of).orElseGet(() -> Namespace.empty()); } + private boolean isViewCatalogEnabled() { + if (!(catalog instanceof ViewCatalog)) { + return false; + } + if (dorisCatalog instanceof IcebergRestExternalCatalog) { + MetastoreProperties metaProps = dorisCatalog.getCatalogProperty().getMetastoreProperties(); + if (metaProps instanceof IcebergRestProperties) { + return ((IcebergRestProperties) metaProps).isIcebergRestViewEnabled(); + } + } + return true; + } + public ThreadPoolExecutor getThreadPoolWithPreAuth() { return dorisCatalog.getThreadPoolWithPreAuth(); } private void performDropView(String remoteDbName, String remoteViewName) throws DdlException { - if (!(catalog instanceof ViewCatalog)) { + if (!isViewCatalogEnabled()) { throw new DdlException("Drop Iceberg view is not supported with not view catalog."); } ViewCatalog viewCatalog = (ViewCatalog) catalog; diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/IcebergRestProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/IcebergRestProperties.java index 887e6d49c5e1a4..eec8ed96125c01 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/IcebergRestProperties.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/IcebergRestProperties.java @@ -110,6 +110,11 @@ public class IcebergRestProperties extends AbstractIcebergProperties { description = "Enable nested namespace for the iceberg rest catalog service.") private String icebergRestNestedNamespaceEnabled = "false"; + @ConnectorProperty(names = {"iceberg.rest.view-enabled"}, + required = false, + description = "Enable view operations for the iceberg rest catalog service.") + private String icebergRestViewEnabled = "true"; + @ConnectorProperty(names = {"iceberg.rest.case-insensitive-name-matching"}, required = false, supported = false, @@ -317,6 +322,10 @@ public boolean isIcebergRestNestedNamespaceEnabled() { return Boolean.parseBoolean(icebergRestNestedNamespaceEnabled); } + public boolean isIcebergRestViewEnabled() { + return Boolean.parseBoolean(icebergRestViewEnabled); + } + public enum Security { NONE, OAUTH2, diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java index 3ecdb9ce437086..2bf1ef3deb4545 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergMetadataOpTest.java @@ -17,10 +17,23 @@ package org.apache.doris.datasource.iceberg; +import org.apache.doris.common.security.authentication.ExecutionAuthenticator; +import org.apache.doris.datasource.CatalogProperty; + +import org.apache.iceberg.catalog.Catalog; import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.SupportsNamespaces; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.catalog.ViewCatalog; import org.junit.Assert; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; public class IcebergMetadataOpTest { @@ -45,4 +58,60 @@ public void testGetNamespaces() { ns = IcebergMetadataOps.getNamespace(Optional.empty(), ""); Assert.assertEquals(0, ns.length()); } + + @Test + public void testListTableNamesSkipsViewsWhenRestViewDisabled() { + IcebergRestExternalCatalog dorisCatalog = Mockito.mock(IcebergRestExternalCatalog.class); + Catalog icebergCatalog = Mockito.mock(Catalog.class, + Mockito.withSettings().extraInterfaces(SupportsNamespaces.class, ViewCatalog.class)); + + Map props = new HashMap<>(); + props.put("type", "iceberg"); + props.put("iceberg.catalog.type", "rest"); + props.put("iceberg.rest.uri", "http://localhost:8181"); + props.put("iceberg.rest.view-enabled", "false"); + + Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new ExecutionAuthenticator() { + }); + Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap()); + Mockito.when(dorisCatalog.getCatalogProperty()).thenReturn(new CatalogProperty(null, props)); + + Namespace namespace = Namespace.of("PUBLIC"); + TableIdentifier table = TableIdentifier.of(namespace, "DORIS_HORIZON_T"); + Mockito.when(icebergCatalog.listTables(namespace)).thenReturn(Collections.singletonList(table)); + + IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog, icebergCatalog); + List tableNames = ops.listTableNames("PUBLIC"); + + Assert.assertEquals(Collections.singletonList("DORIS_HORIZON_T"), tableNames); + Mockito.verify((ViewCatalog) icebergCatalog, Mockito.never()).listViews(Mockito.any()); + } + + @Test + public void testListTableNamesFiltersViewsWhenRestViewEnabled() { + IcebergRestExternalCatalog dorisCatalog = Mockito.mock(IcebergRestExternalCatalog.class); + Catalog icebergCatalog = Mockito.mock(Catalog.class, + Mockito.withSettings().extraInterfaces(SupportsNamespaces.class, ViewCatalog.class)); + + Map props = new HashMap<>(); + props.put("type", "iceberg"); + props.put("iceberg.catalog.type", "rest"); + props.put("iceberg.rest.uri", "http://localhost:8181"); + + Mockito.when(dorisCatalog.getExecutionAuthenticator()).thenReturn(new ExecutionAuthenticator() { + }); + Mockito.when(dorisCatalog.getProperties()).thenReturn(Collections.emptyMap()); + Mockito.when(dorisCatalog.getCatalogProperty()).thenReturn(new CatalogProperty(null, props)); + + Namespace namespace = Namespace.of("PUBLIC"); + TableIdentifier table = TableIdentifier.of(namespace, "DORIS_HORIZON_T"); + TableIdentifier view = TableIdentifier.of(namespace, "DORIS_HORIZON_V"); + Mockito.when(icebergCatalog.listTables(namespace)).thenReturn(Arrays.asList(table, view)); + Mockito.when(((ViewCatalog) icebergCatalog).listViews(namespace)).thenReturn(Collections.singletonList(view)); + + IcebergMetadataOps ops = new IcebergMetadataOps(dorisCatalog, icebergCatalog); + List tableNames = ops.listTableNames("PUBLIC"); + + Assert.assertEquals(Collections.singletonList("DORIS_HORIZON_T"), tableNames); + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/IcebergRestPropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/IcebergRestPropertiesTest.java index faf6f95d3b1d42..8d7082fa115987 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/IcebergRestPropertiesTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/IcebergRestPropertiesTest.java @@ -83,6 +83,23 @@ public void testVendedCredentialsDisabled() { Assertions.assertFalse(catalogProps.containsKey("header.X-Iceberg-Access-Delegation")); } + @Test + public void testRestViewEnabled() { + Map props = new HashMap<>(); + props.put("iceberg.rest.uri", "http://localhost:8080"); + + IcebergRestProperties defaultProps = new IcebergRestProperties(props); + defaultProps.initNormalizeAndCheckProps(); + Assertions.assertTrue(defaultProps.isIcebergRestViewEnabled()); + + props.put("iceberg.rest.view-enabled", "false"); + IcebergRestProperties disabledProps = new IcebergRestProperties(props); + disabledProps.initNormalizeAndCheckProps(); + Assertions.assertFalse(disabledProps.isIcebergRestViewEnabled()); + Assertions.assertFalse(disabledProps.getIcebergRestCatalogProperties() + .containsKey("iceberg.rest.view-enabled")); + } + @Test public void testOAuth2CredentialFlow() { Map props = new HashMap<>();