This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new e5b3528a26 [#13783] fix(trino-connector): Report querying a Gravitino
view as not supported (#13788)
e5b3528a26 is described below
commit e5b3528a2667105cc2e1c4e91e22c0792cb5f005
Author: Yuhui <[email protected]>
AuthorDate: Wed Oct 14 12:16:44 2026 +0800
[#13783] fix(trino-connector): Report querying a Gravitino view as not
supported (#13788)
### What changes were proposed in this pull request?
When a table lookup misses, check whether a Gravitino view with that
name exists and fail with
`NOT_SUPPORTED` instead of returning not found. The view is checked only
on a table miss, so
regular table lookups make no extra request.
Depends on #13787.
### Why are the changes needed?
The connector does not support views, so querying one failed with
`TABLE_NOT_FOUND`, which
suggests the view does not exist.
Fix: #13783
### Does this PR introduce _any_ user-facing change?
Yes, querying a Gravitino view reports `NOT_SUPPORTED`.
### How was this patch tested?
Added `TestGravitinoMetadataGetTableHandle` and
`TestCatalogConnectorMetadataView`.
---
.../trino/connector/GravitinoMetadata.java | 14 ++-
.../catalog/CatalogConnectorMetadata.java | 31 ++++++
.../TestGravitinoMetadataGetTableHandle.java | 105 +++++++++++++++++++++
.../catalog/TestCatalogConnectorMetadataView.java | 100 ++++++++++++++++++++
4 files changed, 249 insertions(+), 1 deletion(-)
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
index 9c37025507..ebc337e410 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoMetadata.java
@@ -149,7 +149,19 @@ public abstract class GravitinoMetadata implements
ConnectorMetadata {
Optional<ConnectorTableVersion> endVersion) {
boolean tableExists =
catalogConnectorMetadata.tableExists(tableName.getSchemaName(),
tableName.getTableName());
- if (!tableExists) return null;
+ if (!tableExists) {
+ // Trino resolves views through getView before reaching here. Gravitino
views are not exposed
+ // through getView, so check for one only on a table miss to keep
regular lookups cheap.
+ if (catalogConnectorMetadata.viewExists(
+ tableName.getSchemaName(), tableName.getTableName())) {
+ throw new TrinoException(
+ NOT_SUPPORTED,
+ String.format(
+ "%s is a view; views are not supported by the Gravitino Trino
connector",
+ tableName));
+ }
+ return null;
+ }
ConnectorTableHandle internalTableHandle =
internalMetadata.getTableHandle(session, tableName, startVersion,
endVersion);
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
index 77c2e2a4c2..c618649b68 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
@@ -45,6 +45,7 @@ import org.apache.gravitino.function.FunctionCatalog;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.TableCatalog;
import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.ViewCatalog;
import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
import org.apache.gravitino.trino.connector.metadata.GravitinoColumn;
@@ -63,6 +64,7 @@ public class CatalogConnectorMetadata {
private final SupportsSchemas schemaCatalog;
private final TableCatalog tableCatalog;
@Nullable private final FunctionCatalog functionCatalog;
+ @Nullable private final ViewCatalog viewCatalog;
/**
* Constructs a new CatalogConnectorMetadata.
@@ -84,6 +86,13 @@ public class CatalogConnectorMetadata {
LOG.debug("Catalog %s does not support function operations",
catalogName);
}
this.functionCatalog = fc;
+ ViewCatalog vc = null;
+ try {
+ vc = catalog.asViewCatalog();
+ } catch (UnsupportedOperationException e) {
+ LOG.debug("Catalog %s does not support view operations", catalogName);
+ }
+ this.viewCatalog = vc;
} catch (NoSuchCatalogException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS,
CATALOG_DOES_NOT_EXIST_MSG, e);
@@ -185,6 +194,28 @@ public class CatalogConnectorMetadata {
}
}
+ /**
+ * Checks if a view exists in the specified schema.
+ *
+ * @param schemaName the name of the schema
+ * @param viewName the name of the view
+ * @return true if the view exists, false otherwise or if the catalog does
not support views
+ */
+ public boolean viewExists(String schemaName, String viewName) {
+ if (viewCatalog == null) {
+ return false;
+ }
+ try {
+ return viewCatalog.viewExists(NameIdentifier.of(schemaName, viewName));
+ } catch (UnsupportedOperationException | NoSuchSchemaException e) {
+ // The client exposes a view catalog for every relational catalog, so a
catalog without view
+ // support (for example JDBC) only reports it when called. A missing
schema has no views.
+ return false;
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
+ }
+ }
+
/**
* Creates a new table in the catalog.
*
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetTableHandle.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetTableHandle.java
new file mode 100644
index 0000000000..4e82d65803
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoMetadataGetTableHandle.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector;
+
+import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.trino.spi.TrinoException;
+import io.trino.spi.connector.ConnectorMetadata;
+import io.trino.spi.connector.ConnectorSession;
+import io.trino.spi.connector.ConnectorTableHandle;
+import io.trino.spi.connector.SchemaTableName;
+import java.util.Optional;
+import org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadata;
+import
org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadataAdapter;
+import org.junit.jupiter.api.Test;
+
+public class TestGravitinoMetadataGetTableHandle {
+
+ private static final SchemaTableName NAME = new SchemaTableName("s", "v");
+
+ @Test
+ public void testViewIsReportedAsNotSupported() {
+ CatalogConnectorMetadata catalogConnectorMetadata =
mock(CatalogConnectorMetadata.class);
+ when(catalogConnectorMetadata.tableExists("s", "v")).thenReturn(false);
+ when(catalogConnectorMetadata.viewExists("s", "v")).thenReturn(true);
+ ConnectorMetadata internalMetadata = mock(ConnectorMetadata.class);
+ GravitinoMetadata metadata = createMetadata(catalogConnectorMetadata,
internalMetadata);
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ metadata.getTableHandle(
+ mock(ConnectorSession.class), NAME, Optional.empty(),
Optional.empty()));
+ assertEquals(NOT_SUPPORTED.toErrorCode(), e.getErrorCode());
+ assertTrue(e.getMessage().contains("is a view"));
+ verify(internalMetadata, never()).getTableHandle(any(), any(), any(),
any());
+ }
+
+ @Test
+ public void testMissingTableAndViewReturnsNull() {
+ CatalogConnectorMetadata catalogConnectorMetadata =
mock(CatalogConnectorMetadata.class);
+ when(catalogConnectorMetadata.tableExists("s", "v")).thenReturn(false);
+ when(catalogConnectorMetadata.viewExists("s", "v")).thenReturn(false);
+ GravitinoMetadata metadata =
+ createMetadata(catalogConnectorMetadata,
mock(ConnectorMetadata.class));
+
+ assertNull(
+ metadata.getTableHandle(
+ mock(ConnectorSession.class), NAME, Optional.empty(),
Optional.empty()));
+ }
+
+ @Test
+ public void testExistingTableDoesNotCheckViews() {
+ CatalogConnectorMetadata catalogConnectorMetadata =
mock(CatalogConnectorMetadata.class);
+ when(catalogConnectorMetadata.tableExists("s", "v")).thenReturn(true);
+ ConnectorMetadata internalMetadata = mock(ConnectorMetadata.class);
+ when(internalMetadata.getTableHandle(any(), any(), any(), any()))
+ .thenReturn(mock(ConnectorTableHandle.class));
+ GravitinoMetadata metadata = createMetadata(catalogConnectorMetadata,
internalMetadata);
+
+ metadata.getTableHandle(mock(ConnectorSession.class), NAME,
Optional.empty(), Optional.empty());
+ verify(catalogConnectorMetadata, never()).viewExists(any(), any());
+ }
+
+ private static GravitinoMetadata createMetadata(
+ CatalogConnectorMetadata catalogConnectorMetadata, ConnectorMetadata
internalMetadata) {
+ return new TestGravitinoMetadata(
+ catalogConnectorMetadata, mock(CatalogConnectorMetadataAdapter.class),
internalMetadata);
+ }
+
+ private static final class TestGravitinoMetadata extends GravitinoMetadata {
+ private TestGravitinoMetadata(
+ CatalogConnectorMetadata catalogConnectorMetadata,
+ CatalogConnectorMetadataAdapter metadataAdapter,
+ ConnectorMetadata internalMetadata) {
+ super(catalogConnectorMetadata, metadataAdapter, internalMetadata);
+ }
+ }
+}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataView.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataView.java
new file mode 100644
index 0000000000..8e11a67d30
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataView.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.trino.connector.catalog;
+
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.trino.spi.TrinoException;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.SupportsSchemas;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.rel.TableCatalog;
+import org.apache.gravitino.rel.ViewCatalog;
+import org.junit.jupiter.api.Test;
+
+public class TestCatalogConnectorMetadataView {
+
+ @Test
+ public void testViewExists() {
+ ViewCatalog viewCatalog = mock(ViewCatalog.class);
+ when(viewCatalog.viewExists(any())).thenReturn(true);
+
+ assertTrue(createMetadata(viewCatalog).viewExists("s", "v"));
+ verify(viewCatalog).viewExists(NameIdentifier.of("s", "v"));
+ }
+
+ @Test
+ public void testCatalogWithoutViewCatalogHasNoViews() {
+ assertFalse(createMetadata(null).viewExists("s", "v"));
+ }
+
+ @Test
+ public void testServerWithoutViewSupportHasNoViews() {
+ ViewCatalog viewCatalog = mock(ViewCatalog.class);
+ when(viewCatalog.viewExists(any()))
+ .thenThrow(new UnsupportedOperationException("Views are not
supported"));
+
+ assertFalse(createMetadata(viewCatalog).viewExists("s", "v"));
+ }
+
+ @Test
+ public void testMissingSchemaHasNoViews() {
+ ViewCatalog viewCatalog = mock(ViewCatalog.class);
+ when(viewCatalog.viewExists(any())).thenThrow(new
NoSuchSchemaException("missing"));
+
+ assertFalse(createMetadata(viewCatalog).viewExists("s", "v"));
+ }
+
+ @Test
+ public void testViewExistsDeniedIsPermissionDenied() {
+ ViewCatalog viewCatalog = mock(ViewCatalog.class);
+ when(viewCatalog.viewExists(any())).thenThrow(new
ForbiddenException("denied"));
+ CatalogConnectorMetadata metadata = createMetadata(viewCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.viewExists("s", "v"));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ }
+
+ private static CatalogConnectorMetadata createMetadata(ViewCatalog
viewCatalog) {
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ Catalog catalog = mock(Catalog.class);
+ when(metalake.loadCatalog(anyString())).thenReturn(catalog);
+ when(catalog.asSchemas()).thenReturn(mock(SupportsSchemas.class));
+ when(catalog.asTableCatalog()).thenReturn(mock(TableCatalog.class));
+ when(catalog.asFunctionCatalog()).thenThrow(new
UnsupportedOperationException("unsupported"));
+ if (viewCatalog == null) {
+ when(catalog.asViewCatalog()).thenThrow(new
UnsupportedOperationException("unsupported"));
+ } else {
+ when(catalog.asViewCatalog()).thenReturn(viewCatalog);
+ }
+ return new CatalogConnectorMetadata(metalake, NameIdentifier.of("m", "c"));
+ }
+}