You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/10/06 15:26:34 UTC
(polaris) branch main updated: NoSQL: honor ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG when dropping catalogs (#5705)
This is an automated email from the ASF dual-hosted git repository.
adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new feba0723a8 NoSQL: honor ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG when dropping catalogs (#5705)
feba0723a8 is described below
commit feba0723a8cf31215db30bc92eb2ae1119c18cce
Author: Alexandre Dutra <ad...@apache.org>
AuthorDate: Tue Oct 6 17:26:28 2026 +0200
NoSQL: honor ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG when dropping catalogs (#5705)
---
CHANGELOG.md | 3 +
.../nosql/metastore/NoSqlMetaStore.java | 9 ++-
.../nosql/metastore/NoSqlMetaStoreManager.java | 30 ++++++++-
.../nosql/metastore/mutation/EntityUpdate.java | 9 ++-
.../nosql/metastore/mutation/MutationAttempt.java | 7 +-
.../nosql/metastore/TestNoSqlMetaStoreManager.java | 77 ++++++++++++++++++++++
6 files changed, 129 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81a13264b0..601f715867 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -184,6 +184,9 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
scheme stripping (e.g. `s3://bucket/path` → `//bucket/path`). `//` and `///` are retained so
scheme-root ancestors remain visible to the overlap check.
- Honored pagination for policy API with applicable-policies endpoint excluded.
+- The NoSQL metastore now honors `ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG`: dropping a
+ non-empty passthrough-facade (federated) catalog previously always failed with NoSQL persistence,
+ even when the flag was enabled.
### Commits
diff --git a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStore.java b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStore.java
index 90e0c9c3c0..2d73c4f6ae 100644
--- a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStore.java
+++ b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStore.java
@@ -404,7 +404,10 @@ class NoSqlMetaStore extends NonFunctionalBasePersistence {
}
DropEntityResult dropEntity(
- PolarisBaseEntity entityToDrop, Map<String, String> cleanupProperties, boolean cleanup) {
+ PolarisBaseEntity entityToDrop,
+ Map<String, String> cleanupProperties,
+ boolean cleanup,
+ boolean dropNonEmptyCatalog) {
requireNonNull(entityToDrop);
LOGGER.atDebug().addArgument(() -> logEntityInfo(entityToDrop)).log("drop entity: {}");
@@ -412,7 +415,9 @@ class NoSqlMetaStore extends NonFunctionalBasePersistence {
var results =
performEntityMutations(
updateKeyForCatalogAndEntityType(entityToDrop),
- List.of(new EntityUpdate(EntityUpdate.Operation.DELETE, entityToDrop, cleanup)));
+ List.of(
+ new EntityUpdate(
+ EntityUpdate.Operation.DELETE, entityToDrop, cleanup, dropNonEmptyCatalog)));
if (cleanup && PolarisEntityType.POLICY == entityToDrop.getType()) {
cleanup = false;
diff --git a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStoreManager.java b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStoreManager.java
index f2741f0143..27aeba3256 100644
--- a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStoreManager.java
+++ b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/NoSqlMetaStoreManager.java
@@ -35,6 +35,7 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.EventEntity;
import org.apache.polaris.core.entity.LocationBasedEntity;
import org.apache.polaris.core.entity.PolarisBaseEntity;
@@ -174,7 +175,34 @@ record NoSqlMetaStoreManager(
@NonNull PolarisBaseEntity entityToDrop,
@Nullable Map<String, String> cleanupProperties,
boolean cleanup) {
- return ms(callCtx).dropEntity(entityToDrop, cleanupProperties, cleanup);
+ // For passthrough facade catalogs, all catalog level entities, except catalog roles, are
+ // passthrough entities that are not source-of-truth, and may optionally be dropped with the
+ // catalog.
+ var passthroughFacadeCatalog = false;
+ var dropNonEmptyCatalog = false;
+ if (entityToDrop.getType() == PolarisEntityType.CATALOG) {
+ var catalogEntity = CatalogEntity.of(entityToDrop);
+ passthroughFacadeCatalog = catalogEntity.isPassthroughFacade();
+ dropNonEmptyCatalog =
+ passthroughFacadeCatalog
+ && callCtx
+ .getRealmConfig()
+ .getConfig(
+ FeatureConfiguration.ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG,
+ catalogEntity);
+ }
+ var result =
+ ms(callCtx).dropEntity(entityToDrop, cleanupProperties, cleanup, dropNonEmptyCatalog);
+ if (passthroughFacadeCatalog
+ && !dropNonEmptyCatalog
+ && result.getReturnStatus() == BaseResult.ReturnStatus.NAMESPACE_NOT_EMPTY) {
+ return new DropEntityResult(
+ BaseResult.ReturnStatus.NAMESPACE_NOT_EMPTY,
+ String.format(
+ "Set %s to true to drop non-empty passthrough facade catalogs",
+ FeatureConfiguration.ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG.key()));
+ }
+ return result;
}
@NonNull
diff --git a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/EntityUpdate.java b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/EntityUpdate.java
index 2534ee741b..713fe65121 100644
--- a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/EntityUpdate.java
+++ b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/EntityUpdate.java
@@ -20,9 +20,14 @@ package org.apache.polaris.persistence.nosql.metastore.mutation;
import org.apache.polaris.core.entity.PolarisBaseEntity;
-public record EntityUpdate(Operation operation, PolarisBaseEntity entity, boolean cleanup) {
+/**
+ * @param dropNonEmptyCatalog for {@link Operation#DELETE} of a catalog, whether the catalog may be
+ * dropped even if it still contains namespaces or tables
+ */
+public record EntityUpdate(
+ Operation operation, PolarisBaseEntity entity, boolean cleanup, boolean dropNonEmptyCatalog) {
public EntityUpdate(Operation operation, PolarisBaseEntity entity) {
- this(operation, entity, false);
+ this(operation, entity, false, false);
}
public enum Operation {
diff --git a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/MutationAttempt.java b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/MutationAttempt.java
index 42d2fb5c5e..64abb91d9a 100644
--- a/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/MutationAttempt.java
+++ b/persistence/nosql/persistence/metastore/src/main/java/org/apache/polaris/persistence/nosql/metastore/mutation/MutationAttempt.java
@@ -219,7 +219,12 @@ public record MutationAttempt(
case CATALOG -> {
var catalogState = memoizedIndexedAccess.catalogContent(entity.getId());
- if (catalogState.nameIndex().map(idx -> idx.iterator().hasNext()).orElse(false)) {
+ // A non-empty catalog may only be dropped if the caller requested it. Its remaining
+ // namespaces and tables are not dropped here: the catalog's content is no longer
+ // reachable once the catalog is gone, and will be eventually purged by
+ // persistence-maintenance.
+ if (!update.dropNonEmptyCatalog()
+ && catalogState.nameIndex().map(idx -> idx.iterator().hasNext()).orElse(false)) {
mutationResults.dropResult(NAMESPACE_NOT_EMPTY);
yield false;
}
diff --git a/persistence/nosql/persistence/metastore/src/test/java/org/apache/polaris/persistence/nosql/metastore/TestNoSqlMetaStoreManager.java b/persistence/nosql/persistence/metastore/src/test/java/org/apache/polaris/persistence/nosql/metastore/TestNoSqlMetaStoreManager.java
index 6b2baee510..a64287634d 100644
--- a/persistence/nosql/persistence/metastore/src/test/java/org/apache/polaris/persistence/nosql/metastore/TestNoSqlMetaStoreManager.java
+++ b/persistence/nosql/persistence/metastore/src/test/java/org/apache/polaris/persistence/nosql/metastore/TestNoSqlMetaStoreManager.java
@@ -45,8 +45,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import org.apache.iceberg.catalog.Namespace;
import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.RealmConfigurationSource;
import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.NamespaceEntity;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntity;
@@ -89,6 +91,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
@SuppressWarnings("CdiInjectionPointsInspection")
@EnableWeld
@@ -517,6 +521,79 @@ public class TestNoSqlMetaStoreManager extends BasePolarisMetaStoreManagerTest {
}
@SuppressWarnings("SameParameterValue")
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void dropNonEmptyPassthroughFacadeCatalog(boolean allowDropping) {
+ RealmConfigurationSource config =
+ (rc, name) ->
+ FeatureConfiguration.ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG
+ .key()
+ .equals(name)
+ ? allowDropping
+ : configurationSource.getConfigValue(rc, name);
+ var ctx = new PolarisCallContext(realmContext, callContext.getMetaStore(), config);
+
+ var catalog =
+ new PolarisBaseEntity.Builder(
+ new PolarisBaseEntity(
+ PolarisEntityConstants.getNullId(),
+ metaStore.generateNewEntityId(ctx).getId(),
+ PolarisEntityType.CATALOG,
+ PolarisEntitySubType.NULL_SUBTYPE,
+ PolarisEntityConstants.getRootEntityId(),
+ "passthroughFacade" + allowDropping))
+ .internalPropertiesAsMap(
+ Map.of(PolarisEntityConstants.getConnectionConfigInfoPropertyName(), "{}"))
+ .build();
+ catalog = metaStore.createCatalog(ctx, catalog, List.of()).getCatalog();
+ assertThat(CatalogEntity.of(catalog).isPassthroughFacade()).isTrue();
+
+ var ns =
+ createEntity(
+ List.of(catalog),
+ PolarisEntityType.NAMESPACE,
+ PolarisEntitySubType.NULL_SUBTYPE,
+ "ns",
+ Map.of());
+ assertThat(ns).extracting(EntityResult::isSuccess, BOOLEAN).isTrue();
+
+ var dropResult = metaStore.dropEntityIfExists(ctx, null, catalog, Map.of(), false);
+ if (allowDropping) {
+ assertThat(dropResult.isSuccess()).isTrue();
+ assertThat(
+ metaStore.readEntityByName(
+ ctx, null, CATALOG, NULL_SUBTYPE, "passthroughFacade" + allowDropping))
+ .extracting(EntityResult::isSuccess, BOOLEAN)
+ .isFalse();
+
+ // The content of the dropped catalog must not leak into a new catalog with the same name.
+ var newCatalog =
+ new PolarisBaseEntity(
+ PolarisEntityConstants.getNullId(),
+ metaStore.generateNewEntityId(ctx).getId(),
+ PolarisEntityType.CATALOG,
+ PolarisEntitySubType.NULL_SUBTYPE,
+ PolarisEntityConstants.getRootEntityId(),
+ "passthroughFacade" + allowDropping);
+ newCatalog = metaStore.createCatalog(ctx, newCatalog, List.of()).getCatalog();
+ assertThat(
+ metaStore
+ .listFullEntities(
+ ctx,
+ List.<PolarisEntityCore>of(newCatalog),
+ PolarisEntityType.NAMESPACE,
+ PolarisEntitySubType.ANY_SUBTYPE,
+ PageToken.readEverything())
+ .items())
+ .isEmpty();
+ } else {
+ assertThat(dropResult.getReturnStatus())
+ .isEqualTo(BaseResult.ReturnStatus.NAMESPACE_NOT_EMPTY);
+ assertThat(dropResult.getExtraInformation())
+ .contains(FeatureConfiguration.ALLOW_DROPPING_NON_EMPTY_PASSTHROUGH_FACADE_CATALOG.key());
+ }
+ }
+
EntityResult createEntity(
List<PolarisBaseEntity> catalogPath,
PolarisEntityType entityType,