yuqi1129 commented on code in PR #13728:
URL: https://github.com/apache/gravitino/pull/13728#discussion_r4229892407
##########
core/src/main/java/org/apache/gravitino/connector/CatalogDropAware.java:
##########
@@ -26,4 +28,24 @@ public interface CatalogDropAware {
/** Performs cleanup after the catalog metadata has been permanently
dropped. */
void onCatalogDropped();
+
+ /**
+ * Performs cleanup after the catalog metadata has been permanently dropped,
when the catalog
+ * registration is unmanaged and its external objects (schemas and the
tables inside them) are
+ * left in place. Implementations should remove the Gravitino identifier
that was written into
+ * those external objects, for example the {@code gravitino.identifier}
property or the "From
+ * Gravitino, DO NOT EDIT" snippet in comments, so that a later catalog that
manages the same
+ * external objects does not inherit stale identifiers.
+ *
+ * <p>Cleanup is best-effort: a failure to clear an identifier must not fail
the drop. The default
+ * implementation only forwards to {@link #onCatalogDropped()}; unmanaged
catalogs that never
+ * write identifiers to external objects do not need to override this method.
+ *
+ * @param remainingExternalSchemas the identifiers of the schemas that still
exist in the external
+ * system after the catalog registration was dropped. The list is empty
for managed catalogs,
+ * whose external objects are dropped together with the registration.
+ */
+ default void onCatalogDropped(List<NameIdentifier> remainingExternalSchemas)
{
+ onCatalogDropped();
Review Comment:
I traced the production implementations to confirm this: no connector
overrides the new overload. The only production implementation of
CatalogDropAware is IcebergCatalogOperations, whose no-arg callback only
removes and closes the in-memory catalog via
IcebergCatalogUtil.removeMemoryCatalog(). HiveCatalogOperations and
JdbcCatalogOperations do not implement CatalogDropAware, so their cleanup
callback is skipped entirely.
Consequently, identifiers stored in external schema/table properties or
comments remain unchanged. Could we add the actual connector cleanup
implementations before claiming Fixes #13475? If this PR is intended only to
introduce the extension point, please describe it as preparatory work and leave
#13475 open.
##########
core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java:
##########
@@ -1417,11 +1417,19 @@ public boolean dropCatalog(NameIdentifier ident,
boolean force)
boolean deleted = store.delete(ident, EntityType.CATALOG, true);
if (deleted) {
markLocalMutation(ident);
+ // Unmanaged catalogs keep their external schemas and tables.
Those objects still
+ // carry the Gravitino identifier written at create/import time,
so a catalog that
+ // later manages them again would inherit stale ownership (see
issue #13475). Collect
+ // the schemas that remain in the external system and let the
connector strip the
+ // identifiers; managed catalogs already dropped their external
objects above, so the
+ // list stays empty.
+ List<NameIdentifier> remainingExternalSchemas =
+ managedStorage ? Collections.emptyList() :
listExternalSchemas(catalogWrapper);
Review Comment:
[Nit] listExternalSchemas() is called before checking whether the connector
implements CatalogDropAware. This adds an external schema-listing call to every
successful unmanaged catalog drop, including Hive/JDBC catalogs that cannot
consume the callback. The call also runs under the metalake write lock after
the metadata deletion has committed; catching exceptions does not avoid waiting
for a slow external system.
Could we check whether the connector supports identifier cleanup before
listing schemas, so connectors without a cleanup implementation retain the
existing drop behavior?
##########
core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java:
##########
@@ -1409,7 +1410,118 @@ void testDropCatalogInvalidatesCacheAfterStoreDelete()
throws Exception {
Assertions.assertTrue(dropped);
Assertions.assertFalse(entityStore.exists(ident, EntityType.CATALOG));
Assertions.assertNull(catalogManager.getCatalogCache().getIfPresent(ident));
- Mockito.verify((CatalogDropAware) operations).onCatalogDropped();
+ // The drop hands the connector the schemas that remain externally (empty
here: the wrapper's
+ // schema operations are stubbed to return nothing), so residual
identifiers can be cleared.
+ Mockito.verify((CatalogDropAware) operations)
+ .onCatalogDropped(Mockito.eq(Collections.emptyList()));
+ }
+
+ @Test
+ void testDropUnmanagedCatalogCleansResidualExternalIdentifiers() throws
Exception {
+ // Regression test for #13475: dropping an unmanaged catalog must tell the
connector which
+ // external schemas remain, so it can clear the Gravitino identifiers
those objects still carry.
+ NameIdentifier ident = NameIdentifier.of("metalake",
"residual_identifier_drop_test");
+ 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);
+ Assertions.assertDoesNotThrow(() -> catalogManager.disableCatalog(ident));
+ BaseCatalog<?> catalog =
catalogManager.loadCatalogAndWrap(ident).catalog();
+
+ NameIdentifier externalSchema = NameIdentifier.of("metalake",
ident.name(), "ext_schema");
+ CatalogManager.CatalogWrapper catalogWrapper =
+ Mockito.mock(CatalogManager.CatalogWrapper.class,
Mockito.RETURNS_DEEP_STUBS);
+ Capability capability = Mockito.mock(Capability.class);
+ CapabilityResult unsupportedResult = CapabilityResult.unsupported("Not
managed");
+ Mockito.when(catalogWrapper.tryAcquire()).thenReturn(true);
+ Mockito.doReturn(catalog).when(catalogWrapper).catalog();
+ Mockito.doReturn(capability).when(catalogWrapper).capabilities();
+ Mockito.doReturn(unsupportedResult).when(capability).managedStorage(any());
+ // The external system still holds the schema after the registration is
dropped.
+ Mockito.doReturn(new NameIdentifier[] {externalSchema})
+ .when(catalogWrapper)
+ .doWithSchemaOps(any());
+
+ CatalogOperations operations =
+ Mockito.mock(
+ CatalogOperations.class,
+ Mockito.withSettings().extraInterfaces(CatalogDropAware.class));
+ Mockito.doAnswer(
+ invocation -> {
+ ThrowableFunction<CatalogOperations, ?> function =
invocation.getArgument(0);
+ return function.apply(operations);
+ })
+ .when(catalogWrapper)
+ .doWithCatalogOps(any());
+
+ catalogManager.getCatalogCache().put(ident, catalogWrapper);
+ boolean dropped = catalogManager.dropCatalog(ident, true);
+
+ Assertions.assertTrue(dropped);
+ Assertions.assertFalse(entityStore.exists(ident, EntityType.CATALOG));
+ // The connector is handed the schemas that remain externally so it can
strip the identifiers.
+ Mockito.verify((CatalogDropAware) operations)
+
.onCatalogDropped(Mockito.eq(Collections.singletonList(externalSchema)));
Review Comment:
[Important] This test verifies that the schema list is passed to a mocked
callback, but does not verify that any residual identifier is removed. It
passes even when the connector performs no cleanup, as is currently the case
for the production implementations.
Please add regression coverage using a real cleanup implementation (with a
controlled external backend if needed): assert that schema and table
identifiers are removed from properties/comments while user metadata is
preserved, and that recreating the catalog does not reuse the old IDs. Please
also cover the advertised best-effort behavior when listing or cleaning
external objects fails.
##########
.codegraph/.gitignore:
##########
@@ -0,0 +1,16 @@
+# CodeGraph data files
Review Comment:
[Nit] This local CodeGraph configuration appears unrelated to the catalog
identifier cleanup. Could we move it to a separate change?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]