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

mchades pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 6e63849e3e [Cherry-pick to branch-1.3] [#13809] fix(core): Clarify 
catalog drop errors for unreachable backends (#13810) (#13812)
6e63849e3e is described below

commit 6e63849e3e5ae4dd226d1639bba8c84db6964977
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 13 16:25:10 2026 +0800

    [Cherry-pick to branch-1.3] [#13809] fix(core): Clarify catalog drop errors 
for unreachable backends (#13810) (#13812)
    
    **Cherry-pick Information:**
    - Original commit: 9223348fc83a781a409333af53b5327817cba2a6 (#13810)
    - Target branch: `branch-1.3`
    - Status: Conflicts resolved in
    4bb90dc6d5d3d16630c82e57dbcd00f349019248.
    
    Resolved import and method-placement conflicts in CatalogManager and its
    tests while preserving the branch-1.3 implementation.
    
    Validation: 50 focused tests passed across TestCatalogManager,
    TestCatalogOperations, and TestExceptionHandlers. Core/server Spotless
    checks, `git diff --check`, and the Java conflict-marker scan passed.
    
    ---------
    
    Co-authored-by: mchades <[email protected]>
---
 .../apache/gravitino/catalog/CatalogManager.java   | 28 ++++++-
 .../gravitino/catalog/TestCatalogManager.java      | 97 +++++++++++++++++++++-
 .../server/web/rest/ExceptionHandlers.java         |  4 +-
 .../server/web/rest/TestCatalogOperations.java     | 28 +++++++
 4 files changed, 151 insertions(+), 6 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index 5c25e4521e..599d9fc735 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -65,6 +65,7 @@ import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import lombok.Getter;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.CatalogChange;
@@ -113,6 +114,7 @@ import org.apache.gravitino.rel.TableCatalog;
 import org.apache.gravitino.rel.ViewCatalog;
 import org.apache.gravitino.storage.IdGenerator;
 import org.apache.gravitino.storage.relational.SupportsEntityChangeLog;
+import org.apache.gravitino.utils.ExceptionMessages;
 import org.apache.gravitino.utils.IsolatedClassLoader;
 import org.apache.gravitino.utils.NamespaceUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
@@ -1284,9 +1286,8 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
             Namespace schemaNs = Namespace.of(ident.namespace().level(0), 
ident.name());
             List<SchemaEntity> schemaEntities =
                 store.list(schemaNs, SchemaEntity.class, EntityType.SCHEMA);
-            if (!force && containsUserCreatedSchemas(schemaEntities, 
catalogWrapper)) {
-              throw new NonEmptyCatalogException(
-                  "Catalog %s has schemas, please drop them first or use force 
option", ident);
+            if (!force) {
+              checkCatalogSchemasBeforeDrop(ident, schemaEntities, 
catalogWrapper);
             }
 
             if (isManagedStorageCatalog(catalogWrapper)) {
@@ -1337,6 +1338,27 @@ public class CatalogManager implements 
CatalogDispatcher, Closeable {
         });
   }
 
+  private void checkCatalogSchemasBeforeDrop(
+      NameIdentifier ident, List<SchemaEntity> schemaEntities, CatalogWrapper 
catalogWrapper)
+      throws Exception {
+    try {
+      if (containsUserCreatedSchemas(schemaEntities, catalogWrapper)) {
+        throw new NonEmptyCatalogException(
+            "Catalog %s has schemas, please drop them first or use force 
option", ident);
+      }
+    } catch (Exception e) {
+      // Some catalogs wrap connection failures, so inspect the cause chain as 
well.
+      if (ExceptionUtils.indexOfType(e, ConnectionFailedException.class) >= 0) 
{
+        throw new ConnectionFailedException(
+            e,
+            "%s. Use force=true to skip this check.",
+            ExceptionMessages.withCause(
+                "Failed to check schemas in the underlying catalog " + ident, 
e));
+      }
+      throw e;
+    }
+  }
+
   /**
    * Check if the given list of schema entities contains any currently 
existing user-created
    * schemas.
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java 
b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
index b415f35bd7..c3f9aa4a67 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
@@ -60,12 +60,14 @@ import org.apache.gravitino.Schema;
 import org.apache.gravitino.connector.BaseCatalog;
 import org.apache.gravitino.connector.CatalogDropAware;
 import org.apache.gravitino.connector.CatalogOperations;
+import org.apache.gravitino.connector.SupportsSchemas;
 import org.apache.gravitino.connector.TestCatalogOperations;
 import org.apache.gravitino.connector.capability.Capability;
 import org.apache.gravitino.connector.capability.CapabilityResult;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
 import org.apache.gravitino.exceptions.CatalogNotInUseException;
 import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
@@ -93,6 +95,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
 
@@ -1116,6 +1120,90 @@ public class TestCatalogManager {
     Assertions.assertTrue(catalogManager.dropCatalog(ident));
   }
 
+  @ParameterizedTest
+  @CsvSource({"false, false", "false, true", "true, false", "true, true"})
+  void testDropCatalogExplainsSchemaConnectionFailure(boolean failOnLoad, 
boolean wrapped)
+      throws Exception {
+    NameIdentifier ident = NameIdentifier.of("metalake", 
"unreachable_catalog");
+    Map<String, String> props =
+        ImmutableMap.of(
+            "provider",
+            "test",
+            PROPERTY_KEY1,
+            "value1",
+            PROPERTY_KEY2,
+            "value2",
+            PROPERTY_KEY5_PREFIX + "1",
+            "value3");
+    catalogManager.createCatalog(ident, Catalog.Type.RELATIONAL, provider, 
"comment", props);
+    catalogManager.disableCatalog(ident);
+    BaseCatalog<?> catalog = 
catalogManager.loadCatalogAndWrap(ident).catalog();
+
+    Namespace schemaNs = Namespace.of("metalake", ident.name());
+    SchemaEntity schemaEntity =
+        SchemaEntity.builder()
+            .withId(RandomIdGenerator.INSTANCE.nextId())
+            .withName("test_schema")
+            .withNamespace(schemaNs)
+            .withAuditInfo(
+                
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+            .build();
+    entityStore.put(schemaEntity);
+
+    ConnectionFailedException connectionFailure =
+        new ConnectionFailedException(
+            new IOException("Connection refused"), "Failed to connect to Hive 
Metastore");
+    RuntimeException failure =
+        wrapped
+            ? new GravitinoRuntimeException(
+                new RuntimeException(connectionFailure), "Failed to connect to 
Hive Metastore")
+            : connectionFailure;
+    SupportsSchemas schemaOps = Mockito.mock(SupportsSchemas.class);
+    if (failOnLoad) {
+      Mockito.when(schemaOps.listSchemas(schemaNs))
+          .thenReturn(new NameIdentifier[] {schemaEntity.nameIdentifier()});
+      
Mockito.when(schemaOps.loadSchema(schemaEntity.nameIdentifier())).thenThrow(failure);
+    } else {
+      Mockito.when(schemaOps.listSchemas(schemaNs)).thenThrow(failure);
+    }
+
+    CatalogManager.CatalogWrapper wrapper = 
Mockito.mock(CatalogManager.CatalogWrapper.class);
+    Capability capability = Mockito.mock(Capability.class);
+    Mockito.when(wrapper.tryAcquire()).thenReturn(true);
+    Mockito.when(wrapper.catalog()).thenReturn(catalog);
+    Mockito.when(wrapper.capabilities()).thenReturn(capability);
+    Mockito.when(capability.managedStorage(any()))
+        .thenReturn(CapabilityResult.unsupported("Not managed"));
+    Mockito.doAnswer(
+            invocation -> {
+              ThrowableFunction<SupportsSchemas, ?> function = 
invocation.getArgument(0);
+              return function.apply(schemaOps);
+            })
+        .when(wrapper)
+        .doWithSchemaOps(any());
+    catalogManager.getCatalogCache().put(ident, wrapper);
+
+    ConnectionFailedException exception =
+        Assertions.assertThrows(
+            ConnectionFailedException.class, () -> 
catalogManager.dropCatalog(ident, false));
+    Assertions.assertEquals(
+        "Failed to check schemas in the underlying catalog 
metalake.unreachable_catalog: "
+            + "Failed to connect to Hive Metastore: Connection refused. "
+            + "Use force=true to skip this check.",
+        exception.getMessage());
+    Assertions.assertSame(failure, exception.getCause());
+    Assertions.assertTrue(entityStore.exists(ident, EntityType.CATALOG));
+    Assertions.assertTrue(entityStore.exists(schemaEntity.nameIdentifier(), 
EntityType.SCHEMA));
+    Mockito.verify(schemaOps).listSchemas(schemaNs);
+    Mockito.verify(schemaOps, Mockito.times(failOnLoad ? 1 : 0))
+        .loadSchema(schemaEntity.nameIdentifier());
+
+    Mockito.clearInvocations(schemaOps);
+    Assertions.assertTrue(catalogManager.dropCatalog(ident, true));
+    Mockito.verifyNoInteractions(schemaOps);
+    Assertions.assertFalse(entityStore.exists(ident, EntityType.CATALOG));
+  }
+
   @Test
   public void testDropCatalogFailsOnSchemaClassificationError() throws 
Exception {
     NameIdentifier ident = NameIdentifier.of("metalake", "test41");
@@ -1156,8 +1244,9 @@ public class TestCatalogManager {
     Mockito.doReturn(catalog).when(wrapper).catalog();
     Mockito.doReturn(capability).when(wrapper).capabilities();
     Mockito.doReturn(unsupportedResult).when(capability).managedStorage(any());
+    RuntimeException failure = new RuntimeException("Failed connect");
     Mockito.doReturn(new NameIdentifier[] {NameIdentifier.of("metalake", 
"test41", "test_schema1")})
-        .doThrow(new RuntimeException("Failed connect"))
+        .doThrow(failure)
         .when(wrapper)
         .doWithSchemaOps(any());
     catalogManager.getCatalogCache().put(ident, wrapper);
@@ -1165,7 +1254,10 @@ public class TestCatalogManager {
     // Unexpected errors during schema classification should propagate 
(fail-closed).
     RuntimeException ex =
         Assertions.assertThrows(RuntimeException.class, () -> 
catalogManager.dropCatalog(ident));
-    Assertions.assertTrue(ex.getCause().getMessage().contains("Failed 
connect"));
+    Assertions.assertSame(failure, ex.getCause());
+    Assertions.assertFalse(ex instanceof ConnectionFailedException);
+    Assertions.assertFalse(ex.getMessage().contains("force=true"));
+    Assertions.assertTrue(entityStore.exists(ident, EntityType.CATALOG));
   }
 
   @Test
@@ -1209,6 +1301,7 @@ public class TestCatalogManager {
         .doWithSchemaOps(any());
     catalogManager.getCatalogCache().put(ident, catalogWrapper);
     Assertions.assertTrue(catalogManager.dropCatalog(ident, true));
+    Mockito.verify(catalogWrapper, Mockito.never()).doWithSchemaOps(any());
   }
 
   @Test
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index a6e3e38a43..36791cf1ba 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -409,7 +409,9 @@ public class ExceptionHandlers {
     @Override
     public Response handle(OperationType op, String catalog, String metalake, 
Exception e) {
       String formatted = StringUtil.isBlank(catalog) ? "" : " [" + catalog + 
"]";
-      String errorMsg = getCatalogErrorMsg(formatted, op.name(), metalake, 
getErrorMsg(e));
+      // Keep schema-check context even when the backend message contains 
"Exception: ".
+      String reason = e instanceof ConnectionFailedException ? e.getMessage() 
: getErrorMsg(e);
+      String errorMsg = getCatalogErrorMsg(formatted, op.name(), metalake, 
reason);
       LOG.warn(errorMsg, e);
 
       if (e instanceof IllegalArgumentException) {
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
index 54b91fdad8..3d2c697502 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
@@ -685,6 +685,34 @@ public class TestCatalogOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse.getType());
   }
 
+  @Test
+  void testDropCatalogWithSchemaConnectionFailure() {
+    String message =
+        "Failed to check schemas in the underlying catalog metalake1.catalog1: 
"
+            + "Failed to connect to Hive Metastore: TTransportException: 
Connection refused. "
+            + "Use force=true to skip this check.";
+    doThrow(new ConnectionFailedException("%s", message))
+        .when(manager)
+        .dropCatalog(NameIdentifier.of("metalake1", "catalog1"), false);
+
+    try (Response response =
+        target("/metalakes/metalake1/catalogs/catalog1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .delete()) {
+      Assertions.assertEquals(Response.Status.BAD_GATEWAY.getStatusCode(), 
response.getStatus());
+      ErrorResponse error = response.readEntity(ErrorResponse.class);
+      Assertions.assertEquals(ErrorConstants.CONNECTION_FAILED_CODE, 
error.getCode());
+      Assertions.assertEquals(ConnectionFailedException.class.getSimpleName(), 
error.getType());
+      Assertions.assertEquals(
+          "Failed to operate catalog(s) [catalog1] operation [DROP] under 
metalake [metalake1], "
+              + "reason ["
+              + message
+              + "]",
+          error.getMessage());
+    }
+  }
+
   @Test
   public void testSetCatalog() {
     // Test enable catalog

Reply via email to