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

jerryshao 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 dc2545f035 [#13278] fix(core): clean up missing schemas on explicit 
cascading drops (#13279)
dc2545f035 is described below

commit dc2545f03594c239e0884bedb58fe862c67f4247
Author: Qi Yu <[email protected]>
AuthorDate: Thu Sep 17 20:28:59 2026 +0800

    [#13278] fix(core): clean up missing schemas on explicit cascading drops 
(#13279)
    
    ### What changes were proposed in this pull request?
    
    Allow an explicit cascading drop to remove an unmanaged schema's stored
    registration when the source schema is already absent. Report success if
    either the source schema or its registration was removed, and clean up
    the schema's write-through secrets.
    
    Preserve the existing behavior of non-cascading drops for missing
    schemas.
    
    ### Why are the changes needed?
    
    A source-side deletion currently leaves a registration that even an
    explicit cascading drop cannot remove.
    
    Fix: #13278
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. A metadata-only cascading schema drop returns `dropped: true`; a
    repeated drop returns false. Stored schema write-through secrets are
    removed with the registration.
    
    ### How was this patch tested?
    
    - `./gradlew spotlessApply`
    - `./gradlew :core:test -PskipITs` — 2,192 passed, 2 skipped.
    - Regression tests cover source-side deletion, repeated drops, preserved
    non-cascading behavior, and write-through secret cleanup.
---
 .../catalog/SchemaOperationDispatcher.java         | 15 +++----
 .../catalog/TestSchemaOperationDispatcher.java     | 51 ++++++++++++++++++++++
 2 files changed, 58 insertions(+), 8 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/SchemaOperationDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/catalog/SchemaOperationDispatcher.java
index 01da4659e4..e2fdd1fdf3 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/SchemaOperationDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/SchemaOperationDispatcher.java
@@ -572,13 +572,12 @@ public class SchemaOperationDispatcher extends 
OperationDispatcher implements Sc
             return droppedFromCatalog;
           }
 
-          // A false result is ambiguous: the external schema may have been 
renamed or dropped out
-          // of band. Preserve the registration because deleting it after a 
rename would lose
-          // Gravitino-only metadata. A true out-of-band drop can therefore 
leave a stale
-          // registration that requires separate cleanup.
-          if (droppedFromCatalog) {
+          // A non-cascading drop preserves a missing registration because the 
source schema
+          // may have been renamed. An explicit cascading drop also removes 
stale metadata.
+          boolean droppedFromStore = false;
+          if (droppedFromCatalog || cascade) {
             try {
-              store.delete(ident, SCHEMA, true);
+              droppedFromStore = store.delete(ident, SCHEMA, true);
             } catch (NoSuchEntityException e) {
               LOG.warn("The schema to be dropped does not exist in the store: 
{}", ident, e);
             } catch (Exception e) {
@@ -595,10 +594,10 @@ public class SchemaOperationDispatcher extends 
OperationDispatcher implements Sc
                       catalogIdent,
                       c -> c.doWithSchemaOps(s -> s.schemaExists(schemaIdent)),
                       RuntimeException.class));
-          if (droppedFromCatalog) {
+          if (droppedFromCatalog || droppedFromStore) {
             secretManager.deleteSecretsFromProperties(schemaProperties);
           }
-          return droppedFromCatalog;
+          return droppedFromCatalog || droppedFromStore;
         });
   }
 
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestSchemaOperationDispatcher.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestSchemaOperationDispatcher.java
index a631e8fbbb..a04c549a4c 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestSchemaOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestSchemaOperationDispatcher.java
@@ -400,6 +400,57 @@ public class TestSchemaOperationDispatcher extends 
TestOperationDispatcher {
     Assertions.assertTrue(entityStore.exists(schemaIdent, SCHEMA));
   }
 
+  @Test
+  void testDropSchemaRemovedFromSourceReportsMetadataCleanup() throws 
Exception {
+    reset(entityStore);
+    NameIdentifier ident = NameIdentifier.of(metalake, catalog, 
"externally_dropped_schema");
+    dispatcher.createSchema(ident, "comment", ImmutableMap.of("k1", "v1", 
"k2", "v2"));
+    boolean droppedFromSource =
+        catalogManager.doWithCatalogWrapper(
+            NameIdentifier.of(metalake, catalog),
+            wrapper -> wrapper.doWithSchemaOps(ops -> ops.dropSchema(ident, 
true)));
+    Assertions.assertTrue(droppedFromSource);
+    Assertions.assertTrue(entityStore.exists(ident, SCHEMA));
+
+    Assertions.assertTrue(dispatcher.dropSchema(ident, true));
+    Assertions.assertFalse(entityStore.exists(ident, SCHEMA));
+    Assertions.assertFalse(dispatcher.dropSchema(ident, true));
+  }
+
+  @Test
+  void testCascadingDropOfMissingSchemaDeletesStoredSecrets() throws Exception 
{
+    reset(entityStore);
+    try (SecretManager secrets = memorySecretManager()) {
+      SchemaOperationDispatcher d =
+          new SchemaOperationDispatcher(catalogManager, entityStore, 
idGenerator, secrets);
+      NameIdentifier ident = NameIdentifier.of(metalake, catalog, 
"missing_schema_secret");
+      d.createSchema(
+          ident,
+          "comment",
+          ImmutableMap.of("k1", "v1"),
+          Map.of("k2", new SecretBinding("memory", "s3cr3t")),
+          Map.of());
+      SchemaEntity entity = entityStore.get(ident, SCHEMA, SchemaEntity.class);
+      SecretUrn urn =
+          SecretUrn.buildWriteThrough(
+              "memory",
+              Map.of(
+                  SecretConstants.ATTR_ENTITY_TYPE, "schema",
+                  SecretConstants.ATTR_ENTITY_ID, String.valueOf(entity.id()),
+                  SecretConstants.ATTR_PROPERTY_KEY, "k2"));
+      boolean sourceDropped =
+          catalogManager.doWithCatalogWrapper(
+              NameIdentifier.of(metalake, catalog),
+              wrapper -> wrapper.doWithSchemaOps(ops -> ops.dropSchema(ident, 
true)));
+      Assertions.assertTrue(sourceDropped);
+      Assertions.assertFalse(d.dropSchema(ident, false));
+      Assertions.assertEquals("s3cr3t", secrets.readSecret(urn));
+      Assertions.assertTrue(d.dropSchema(ident, true));
+      Assertions.assertFalse(entityStore.exists(ident, SCHEMA));
+      Assertions.assertThrows(IllegalArgumentException.class, () -> 
secrets.readSecret(urn));
+    }
+  }
+
   @Test
   public void testDropHierarchicalSchemaCleansUpOrphanedAncestors() throws 
IOException {
     // Clear any spy stubs leaked from other tests sharing the static 
entityStore.

Reply via email to