You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by yu...@apache.org on 2026/08/31 21:54:47 UTC
(polaris) branch main updated: Service: Fix namespace creation rejecting its own default location (#5357)
This is an automated email from the ASF dual-hosted git repository.
flyrain 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 e0a4306933 Service: Fix namespace creation rejecting its own default location (#5357)
e0a4306933 is described below
commit e0a4306933b563584892ba98cb6c454b5ac1b77b
Author: Ayush Saxena <ay...@apache.org>
AuthorDate: Tue Sep 1 03:24:41 2026 +0530
Service: Fix namespace creation rejecting its own default location (#5357)
---
CHANGELOG.md | 7 +++
.../catalog/iceberg/LocalIcebergCatalog.java | 31 ++++++-------
.../iceberg/IcebergAllowedLocationTest.java | 52 ++++++++++++++++++++++
3 files changed, 72 insertions(+), 18 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32fd290184..69998f343f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -138,6 +138,13 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
loading the grants held by a principal, principal role or catalog role scans every grant record
in the realm, because the `grant_records` primary key continues with the securable columns after
`realm_id`. Existing CockroachDB deployments need a manual index creation — see Upgrade notes.
+- Creating a namespace without an explicit location no longer fails with HTTP 400 when the
+ catalog's `default-base-location` sits inside an allowed location instead of being one of
+ them. For example, with allowed location `s3://b1` and `default-base-location` `s3://b1/d1`,
+ `CREATE NAMESPACE ns` places the namespace at `s3://b1/d1/ns`, but the check expected it
+ directly under an allowed location, at `s3://b1/ns`, and rejected it as a custom location even
+ though the request asked for none. The namespace location is now compared against the
+ catalog's `default-base-location`, which is what it is derived from.
### Commits
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
index 58044461c0..f400eabf7f 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
@@ -702,7 +702,7 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
}
if (!realmConfig.getConfig(
BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION, catalogEntity)) {
- validateNamespaceLocation(entity, resolvedParent);
+ validateNamespaceUsesDefaultLocation(entity, resolvedParent);
}
EntityResult result =
getMetaStoreManager()
@@ -899,7 +899,7 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
if (!realmConfig.getConfig(
BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION, catalogEntity)) {
if (properties.containsKey(PolarisEntityConstants.ENTITY_BASE_LOCATION)) {
- validateNamespaceLocation(NamespaceEntity.of(entity), resolvedEntities);
+ validateNamespaceUsesDefaultLocation(NamespaceEntity.of(entity), resolvedEntities);
}
}
@@ -1439,8 +1439,8 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
}
}
- /** Checks whether the location of a namespace is valid given its parent */
- private void validateNamespaceLocation(
+ /** Checks that a namespace sits at the default location derived from its parent. */
+ private void validateNamespaceUsesDefaultLocation(
NamespaceEntity namespace, PolarisResolvedPathWrapper resolvedParent) {
StorageLocation namespaceLocation =
StorageLocation.of(
@@ -1462,24 +1462,19 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
throw new IllegalArgumentException(
"Cannot create namespace without a parent storage configuration");
}
- List<StorageLocation> defaultLocations =
- parentEntity.getStorageConfigurationInfo().getAllowedLocations().stream()
- .filter(java.util.Objects::nonNull)
- .map(
- l ->
- StorageLocation.ensureTrailingSlash(
- StorageLocation.ensureTrailingSlash(l) + namespace.getName()))
- .map(StorageLocation::of)
- .toList();
- if (!defaultLocations.contains(namespaceLocation)) {
+ String parentLocation = resolveLocationForPath(diagnostics, List.of(parent));
+ StorageLocation defaultLocation =
+ StorageLocation.of(
+ StorageLocation.ensureTrailingSlash(
+ StorageLocation.ensureTrailingSlash(parentLocation) + namespace.getName()));
+ if (!defaultLocation.equals(namespaceLocation)) {
throw new IllegalArgumentException(
"Namespace "
+ namespace.getName()
+ " has a custom location, "
- + "which is not enabled. Expected a location in: ["
- + String.join(
- ", ", defaultLocations.stream().map(StorageLocation::toString).toList())
- + "]. Got location: "
+ + "which is not enabled. Expected location: ["
+ + defaultLocation
+ + "]. Got location: ["
+ namespaceLocation
+ "]");
}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
index aef8b9eb75..dacd3055ca 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
@@ -678,6 +678,58 @@ public class IcebergAllowedLocationTest {
.hasMessageContaining("Invalid locations");
}
+ @Test
+ void testCreateNamespaceWhenBaseLocationIsNestedInsideAllowedLocation(@TempDir Path tmpDir) {
+ TestServices services =
+ TestServices.builder()
+ .config(
+ Map.of(
+ "ALLOW_INSECURE_STORAGE_TYPES",
+ "true",
+ "SUPPORTED_CATALOG_STORAGE_TYPES",
+ List.of("FILE")))
+ .build();
+
+ String allowedLocation = tmpDir.toAbsolutePath().toUri().toString();
+ String catalogLocation = tmpDir.resolve("warehouse").toAbsolutePath().toUri().toString();
+ createCatalog(services, Map.of(), catalogLocation, List.of(allowedLocation));
+
+ CreateNamespaceRequest derivedLocationRequest =
+ CreateNamespaceRequest.builder().withNamespace(Namespace.of(namespace)).build();
+ try (Response response =
+ services
+ .restApi()
+ .createNamespace(
+ catalog,
+ derivedLocationRequest,
+ IDEMPOTENCY_KEY,
+ services.realmContext(),
+ services.securityContext())) {
+ assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ }
+
+ // A caller-specified location must still be rejected.
+ Map<String, String> customLocation = new HashMap<>();
+ customLocation.put("location", allowedLocation + "elsewhere");
+ CreateNamespaceRequest customLocationRequest =
+ CreateNamespaceRequest.builder()
+ .withNamespace(Namespace.of("ns2"))
+ .setProperties(customLocation)
+ .build();
+ assertThatThrownBy(
+ () ->
+ services
+ .restApi()
+ .createNamespace(
+ catalog,
+ customLocationRequest,
+ IDEMPOTENCY_KEY,
+ services.realmContext(),
+ services.securityContext()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("has a custom location");
+ }
+
private void createCatalog(
TestServices services,
Map<String, String> catalogConfig,