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"));
+  }
+}

Reply via email to