This is an automated email from the ASF dual-hosted git repository.
emaynard 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 b93e97b9c Introduce behavior-change configs as an alternative to
feature configs (#1124)
b93e97b9c is described below
commit b93e97b9c5c97cb813696df50aee7550f21b155c
Author: Eric Maynard <[email protected]>
AuthorDate: Wed Mar 12 10:50:06 2025 -0700
Introduce behavior-change configs as an alternative to feature configs
(#1124)
* builds
* builds, tests fail
* autolint
* some fixes
* autolint
* style
* working with duplicated code
* autolint
* working with duplicated code
* revert
* autolint
* autolint
* add basic test
* autolint
* better comment
* autolint
---
.../PolarisEclipseLinkMetaStoreManagerTest.java | 2 +-
.../it/test/PolarisApplicationIntegrationTest.java | 5 +-
.../PolarisManagementServiceIntegrationTest.java | 5 +-
.../it/test/PolarisRestCatalogIntegrationTest.java | 11 +-
.../PolarisRestCatalogViewIntegrationBase.java | 10 +-
.../it/test/PolarisSparkIntegrationTest.java | 5 +-
.../apache/polaris/core/PolarisCallContext.java | 1 +
.../polaris/core/auth/PolarisAuthorizerImpl.java | 6 +-
.../core/config/BehaviorChangeConfiguration.java | 46 ++++++
.../FeatureConfiguration.java} | 175 +++++----------------
.../polaris/core/config/PolarisConfiguration.java | 141 +++++++++++++++++
.../{ => config}/PolarisConfigurationStore.java | 3 +-
.../storage/PolarisStorageConfigurationInfo.java | 4 +-
.../aws/AwsCredentialsStorageIntegration.java | 4 +-
.../azure/AzureCredentialsStorageIntegration.java | 4 +-
.../core/storage/cache/StorageCredentialCache.java | 11 +-
...TreeMapAtomicOperationMetaStoreManagerTest.java | 2 +-
.../PolarisTreeMapMetaStoreManagerTest.java | 2 +-
.../storage/InMemoryStorageIntegrationTest.java | 2 +-
.../storage/PolarisConfigurationStoreTest.java | 53 ++++++-
.../polaris/admintool/config/QuarkusProducers.java | 2 +-
.../QuarkusBehaviorChangesConfiguration.java | 91 +++++++++++
.../config/QuarkusFeaturesConfiguration.java | 9 +-
.../service/quarkus/config/QuarkusProducers.java | 2 +-
.../quarkus/admin/PolarisAuthzTestBase.java | 6 +-
.../quarkus/admin/PolarisOverlappingTableTest.java | 4 +-
.../quarkus/catalog/BasePolarisCatalogTest.java | 34 ++--
.../catalog/BasePolarisCatalogViewTest.java | 8 +-
.../PolarisCatalogHandlerWrapperAuthzTest.java | 2 +-
.../quarkus/test/PolarisIntegrationTestHelper.java | 2 +-
.../polaris/service/admin/PolarisAdminService.java | 6 +-
.../polaris/service/admin/PolarisServiceImpl.java | 4 +-
.../service/catalog/BasePolarisCatalog.java | 52 +++---
.../service/catalog/IcebergCatalogAdapter.java | 2 +-
.../catalog/PolarisCatalogHandlerWrapper.java | 10 +-
.../service/catalog/io/DefaultFileIOFactory.java | 2 +-
.../polaris/service/catalog/io/FileIOUtil.java | 8 +-
.../catalog/io/WasbTranslatingFileIOFactory.java | 2 +-
.../service/config/DefaultConfigurationStore.java | 2 +-
.../context/DefaultCallContextResolver.java | 2 +-
.../org/apache/polaris/service/TestServices.java | 2 +-
.../service/catalog/io/MeasuredFileIOFactory.java | 2 +-
42 files changed, 499 insertions(+), 247 deletions(-)
diff --git
a/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
b/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
index 9415696da..8e63a77db 100644
---
a/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
+++
b/extension/persistence/eclipselink/src/test/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreManagerTest.java
@@ -36,9 +36,9 @@ import java.util.Comparator;
import java.util.Objects;
import java.util.stream.Stream;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.persistence.BasePolarisMetaStoreManagerTest;
import org.apache.polaris.core.persistence.PolarisTestMetaStoreManager;
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
index 638e4b582..0be4312c0 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
@@ -89,10 +89,11 @@ import org.junit.jupiter.api.io.TempDir;
/**
* @implSpec This test expects the server to be configured with the following
features configured:
* <ul>
- * <li>{@link
org.apache.polaris.core.PolarisConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
+ * <li>{@link
+ *
org.apache.polaris.core.config.FeatureConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
* {@code true}
* <li>{@link
- *
org.apache.polaris.core.PolarisConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}:
+ *
org.apache.polaris.core.config.FeatureConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}:
* {@code true}
* </ul>
* The server must also be configured to reject request body sizes larger
than 1MB (1000000
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java
index e830a18ab..d743351d9 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java
@@ -90,10 +90,11 @@ import org.testcontainers.shaded.org.awaitility.Awaitility;
* @implSpec @implSpec This test expects the server to be configured with the
following features
* configured:
* <ul>
- * <li>{@link
org.apache.polaris.core.PolarisConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
+ * <li>{@link
+ *
org.apache.polaris.core.config.FeatureConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
* {@code true}
* <li>{@link
- *
org.apache.polaris.core.PolarisConfiguration#ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING}:
+ *
org.apache.polaris.core.config.FeatureConfiguration#ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING}:
* {@code true}
* </ul>
*/
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
index 2a31f2f11..a832553cf 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
@@ -58,7 +58,6 @@ import org.apache.iceberg.io.ResolvingFileIO;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.rest.responses.ErrorResponse;
import org.apache.iceberg.types.Types;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogGrant;
@@ -76,6 +75,8 @@ import org.apache.polaris.core.admin.model.TableGrant;
import org.apache.polaris.core.admin.model.TablePrivilege;
import org.apache.polaris.core.admin.model.ViewGrant;
import org.apache.polaris.core.admin.model.ViewPrivilege;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfiguration;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
import org.apache.polaris.service.it.env.CatalogApi;
@@ -449,7 +450,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
Catalog catalog = managementApi.getCatalog(currentCatalogName);
Map<String, String> catalogProps = new
HashMap<>(catalog.getProperties().toMap());
catalogProps.put(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
managementApi.updateCatalog(catalog, catalogProps);
restCatalog.createNamespace(Namespace.of("ns1"));
@@ -477,7 +478,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
Catalog catalog = managementApi.getCatalog(currentCatalogName);
Map<String, String> catalogProps = new
HashMap<>(catalog.getProperties().toMap());
catalogProps.put(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
managementApi.updateCatalog(catalog, catalogProps);
restCatalog.createNamespace(Namespace.of("ns1"));
@@ -514,7 +515,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
Catalog catalog = managementApi.getCatalog(currentCatalogName);
Map<String, String> catalogProps = new
HashMap<>(catalog.getProperties().toMap());
catalogProps.put(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
"false");
managementApi.updateCatalog(catalog, catalogProps);
restCatalog.createNamespace(Namespace.of("ns1"));
@@ -563,7 +564,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("Access Delegation is not enabled for this
catalog")
.hasMessageContaining(
-
PolarisConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING.catalogConfig());
+
FeatureConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING.catalogConfig());
} finally {
resolvingFileIO.deleteFile(fileLocation);
}
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
index 1fb24f5fe..28c38c99e 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
@@ -24,12 +24,12 @@ import java.lang.reflect.Method;
import java.util.Map;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.view.ViewCatalogTests;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogProperties;
import org.apache.polaris.core.admin.model.PolarisCatalog;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.service.it.env.ClientCredentials;
import org.apache.polaris.service.it.env.IcebergHelper;
@@ -50,8 +50,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
* client.
*
* @implSpec This test expects the server to be configured with {@link
- *
org.apache.polaris.core.PolarisConfiguration#SUPPORTED_CATALOG_STORAGE_TYPES}
set to the
- * appropriate storage type.
+ *
org.apache.polaris.core.config.FeatureConfiguration#SUPPORTED_CATALOG_STORAGE_TYPES}
set to
+ * the appropriate storage type.
*/
@ExtendWith(PolarisIntegrationTestExtension.class)
public abstract class PolarisRestCatalogViewIntegrationBase extends
ViewCatalogTests<RESTCatalog> {
@@ -99,9 +99,9 @@ public abstract class PolarisRestCatalogViewIntegrationBase
extends ViewCatalogT
CatalogProperties.builder(defaultBaseLocation)
.addProperty(
CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:")
-
.addProperty(PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
+
.addProperty(FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.build();
Catalog catalog =
PolarisCatalog.builder()
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisSparkIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisSparkIntegrationTest.java
index 3625c69e8..7333baa39 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisSparkIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisSparkIntegrationTest.java
@@ -64,9 +64,10 @@ import org.slf4j.LoggerFactory;
* @implSpec This test expects the server to be configured with the following
features enabled:
* <ul>
* <li>{@link
- *
org.apache.polaris.core.PolarisConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}:
+ *
org.apache.polaris.core.config.FeatureConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}:
* {@code true}
- * <li>{@link
org.apache.polaris.core.PolarisConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
+ * <li>{@link
+ *
org.apache.polaris.core.config.FeatureConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}:
* {@code true}
* </ul>
*/
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/PolarisCallContext.java
b/polaris-core/src/main/java/org/apache/polaris/core/PolarisCallContext.java
index 3b5e96251..510cd0f6c 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/PolarisCallContext.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/PolarisCallContext.java
@@ -21,6 +21,7 @@ package org.apache.polaris.core;
import jakarta.annotation.Nonnull;
import java.time.Clock;
import java.time.ZoneId;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.persistence.BasePersistence;
/**
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
index 96f449acb..564be49da 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java
@@ -98,8 +98,8 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.iceberg.exceptions.ForbiddenException;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
@@ -510,7 +510,7 @@ public class PolarisAuthorizerImpl implements
PolarisAuthorizer {
boolean enforceCredentialRotationRequiredState =
featureConfig.getConfiguration(
CallContext.getCurrentContext().getPolarisCallContext(),
-
PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING);
+
FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING);
if (enforceCredentialRotationRequiredState
&& authenticatedPrincipal
.getPrincipalEntity()
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
new file mode 100644
index 000000000..a64c59402
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.core.config;
+
+import java.util.Optional;
+
+/**
+ * Internal configuration flags for non-feature behavior changes in Polaris.
These flags control
+ * subtle behavior adjustments and bug fixes, not user-facing catalog
settings. They are intended
+ * for internal use only, are inherently unstable, and may be removed at any
time. When introducing
+ * a new flag, consider the trade-off between maintenance burden and the risk
of an unguarded
+ * behavior change. Flags here are generally short-lived and should either be
removed or promoted to
+ * stable feature flags before the next release.
+ *
+ * @param <T> The type of the configuration
+ */
+public class BehaviorChangeConfiguration<T> extends PolarisConfiguration<T> {
+
+ protected BehaviorChangeConfiguration(
+ String key, String description, T defaultValue, Optional<String>
catalogConfig) {
+ super(key, description, defaultValue, catalogConfig);
+ }
+
+ public static final BehaviorChangeConfiguration<Boolean>
VALIDATE_VIEW_LOCATION_OVERLAP =
+ PolarisConfiguration.<Boolean>builder()
+ .key("STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS")
+ .description("If true, validate that view locations don't overlap
when views are created")
+ .defaultValue(true)
+ .buildBehaviorChangeConfiguration();
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/PolarisConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
similarity index 61%
rename from
polaris-core/src/main/java/org/apache/polaris/core/PolarisConfiguration.java
rename to
polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
index ca1962e3c..087d0525e 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/PolarisConfiguration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
@@ -16,115 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.core;
+package org.apache.polaris.core.config;
-import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
-import org.apache.polaris.core.context.CallContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-public class PolarisConfiguration<T> {
-
- private static final Logger LOGGER =
LoggerFactory.getLogger(PolarisConfiguration.class);
-
- public final String key;
- public final String description;
- public final T defaultValue;
- private final Optional<String> catalogConfigImpl;
- private final Class<T> typ;
-
- @SuppressWarnings("unchecked")
- public PolarisConfiguration(
+/**
+ * Configurations for features within Polaris. These configurations are
intended to be customized
+ * and many expose user-facing catalog-level configurations. These
configurations are stable over
+ * time.
+ *
+ * @param <T>
+ */
+public class FeatureConfiguration<T> extends PolarisConfiguration<T> {
+ protected FeatureConfiguration(
String key, String description, T defaultValue, Optional<String>
catalogConfig) {
- this.key = key;
- this.description = description;
- this.defaultValue = defaultValue;
- this.catalogConfigImpl = catalogConfig;
- this.typ = (Class<T>) defaultValue.getClass();
- }
-
- public boolean hasCatalogConfig() {
- return catalogConfigImpl.isPresent();
- }
-
- public String catalogConfig() {
- return catalogConfigImpl.orElseThrow(
- () ->
- new IllegalStateException(
- "Attempted to read a catalog config key from a configuration
that doesn't have one."));
- }
-
- T cast(Object value) {
- return this.typ.cast(value);
- }
-
- public static class Builder<T> {
- private String key;
- private String description;
- private T defaultValue;
- private Optional<String> catalogConfig = Optional.empty();
-
- public Builder<T> key(String key) {
- this.key = key;
- return this;
- }
-
- public Builder<T> description(String description) {
- this.description = description;
- return this;
- }
-
- @SuppressWarnings("unchecked")
- public Builder<T> defaultValue(T defaultValue) {
- if (defaultValue instanceof List<?>) {
- // Type-safe handling of List
- this.defaultValue = (T) new ArrayList<>((List<?>) defaultValue);
- } else {
- this.defaultValue = defaultValue;
- }
- return this;
- }
-
- public Builder<T> catalogConfig(String catalogConfig) {
- this.catalogConfig = Optional.of(catalogConfig);
- return this;
- }
-
- public PolarisConfiguration<T> build() {
- if (key == null || description == null || defaultValue == null) {
- throw new IllegalArgumentException("key, description, and defaultValue
are required");
- }
- return new PolarisConfiguration<>(key, description, defaultValue,
catalogConfig);
- }
- }
-
- /**
- * Returns the value of a `PolarisConfiguration`, or the default if it
cannot be loaded. This
- * method does not need to be used when a `CallContext` is already available
- */
- public static <T> T loadConfig(PolarisConfiguration<T> configuration) {
- var callContext = CallContext.getCurrentContext();
- if (callContext == null) {
- LOGGER.warn(
- String.format(
- "Unable to load current call context; using %s = %s",
- configuration.key, configuration.defaultValue));
- return configuration.defaultValue;
- }
- return callContext
- .getPolarisCallContext()
- .getConfigurationStore()
- .getConfiguration(callContext.getPolarisCallContext(), configuration);
- }
-
- public static <T> Builder<T> builder() {
- return new Builder<>();
+ super(key, description, defaultValue, catalogConfig);
}
- public static final PolarisConfiguration<Boolean>
+ public static final FeatureConfiguration<Boolean>
ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING =
PolarisConfiguration.<Boolean>builder()
.key("ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING")
@@ -132,9 +43,9 @@ public class PolarisConfiguration<T> {
"If set to true, require that principals must rotate their
credentials before being used "
+ "for anything else.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION =
+ public static final FeatureConfiguration<Boolean>
SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION =
PolarisConfiguration.<Boolean>builder()
.key("SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION")
.description(
@@ -147,9 +58,9 @@ public class PolarisConfiguration<T> {
+ " \"credential-vending\" and can use server-default
environment variables or credential config\n"
+ " files for all storage access, or in test/dev
scenarios.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_TABLE_LOCATION_OVERLAP =
+ public static final FeatureConfiguration<Boolean>
ALLOW_TABLE_LOCATION_OVERLAP =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_TABLE_LOCATION_OVERLAP")
.catalogConfig("allow.overlapping.table.location")
@@ -157,58 +68,58 @@ public class PolarisConfiguration<T> {
"If set to true, allow one table's location to reside within
another table's location. "
+ "This is only enforced within a given namespace.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_NAMESPACE_LOCATION_OVERLAP =
+ public static final FeatureConfiguration<Boolean>
ALLOW_NAMESPACE_LOCATION_OVERLAP =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_NAMESPACE_LOCATION_OVERLAP")
.description(
"If set to true, allow one namespace's location to reside within
another namespace's location. "
+ "This is only enforced within a parent catalog or
namespace.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_EXTERNAL_METADATA_FILE_LOCATION =
+ public static final FeatureConfiguration<Boolean>
ALLOW_EXTERNAL_METADATA_FILE_LOCATION =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_EXTERNAL_METADATA_FILE_LOCATION")
.description(
"If set to true, allows metadata files to be located outside the
default metadata directory.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_OVERLAPPING_CATALOG_URLS =
+ public static final FeatureConfiguration<Boolean>
ALLOW_OVERLAPPING_CATALOG_URLS =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_OVERLAPPING_CATALOG_URLS")
.description("If set to true, allows catalog URLs to overlap.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_UNSTRUCTURED_TABLE_LOCATION =
+ public static final FeatureConfiguration<Boolean>
ALLOW_UNSTRUCTURED_TABLE_LOCATION =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_UNSTRUCTURED_TABLE_LOCATION")
.catalogConfig("allow.unstructured.table.location")
.description("If set to true, allows unstructured table locations.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_EXTERNAL_TABLE_LOCATION =
+ public static final FeatureConfiguration<Boolean>
ALLOW_EXTERNAL_TABLE_LOCATION =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_EXTERNAL_TABLE_LOCATION")
.catalogConfig("allow.external.table.location")
.description(
"If set to true, allows tables to have external locations
outside the default structure.")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean>
ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING =
+ public static final FeatureConfiguration<Boolean>
ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING")
.catalogConfig("enable.credential.vending")
.description("If set to true, allow credential vending for external
catalogs.")
.defaultValue(true)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<List<String>>
SUPPORTED_CATALOG_STORAGE_TYPES =
+ public static final FeatureConfiguration<List<String>>
SUPPORTED_CATALOG_STORAGE_TYPES =
PolarisConfiguration.<List<String>>builder()
.key("SUPPORTED_CATALOG_STORAGE_TYPES")
.catalogConfig("supported.storage.types")
@@ -219,34 +130,34 @@ public class PolarisConfiguration<T> {
StorageConfigInfo.StorageTypeEnum.AZURE.name(),
StorageConfigInfo.StorageTypeEnum.GCS.name(),
StorageConfigInfo.StorageTypeEnum.FILE.name()))
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean> CLEANUP_ON_NAMESPACE_DROP =
+ public static final FeatureConfiguration<Boolean> CLEANUP_ON_NAMESPACE_DROP =
PolarisConfiguration.<Boolean>builder()
.key("CLEANUP_ON_NAMESPACE_DROP")
.catalogConfig("cleanup.on.namespace.drop")
.description("If set to true, clean up data when a namespace is
dropped")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean> CLEANUP_ON_CATALOG_DROP =
+ public static final FeatureConfiguration<Boolean> CLEANUP_ON_CATALOG_DROP =
PolarisConfiguration.<Boolean>builder()
.key("CLEANUP_ON_CATALOG_DROP")
.catalogConfig("cleanup.on.catalog.drop")
.description("If set to true, clean up data when a catalog is
dropped")
.defaultValue(false)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Boolean> DROP_WITH_PURGE_ENABLED =
+ public static final FeatureConfiguration<Boolean> DROP_WITH_PURGE_ENABLED =
PolarisConfiguration.<Boolean>builder()
.key("DROP_WITH_PURGE_ENABLED")
.catalogConfig("drop-with-purge.enabled")
.description(
"If set to true, allows tables to be dropped with the purge
parameter set to true.")
.defaultValue(true)
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Integer>
STORAGE_CREDENTIAL_DURATION_SECONDS =
+ public static final FeatureConfiguration<Integer>
STORAGE_CREDENTIAL_DURATION_SECONDS =
PolarisConfiguration.<Integer>builder()
.key("STORAGE_CREDENTIAL_DURATION_SECONDS")
.description(
@@ -254,22 +165,22 @@ public class PolarisConfiguration<T> {
+ " longer (or shorter) durations is dependent on the
storage provider. GCS"
+ " current does not respect this value.")
.defaultValue(60 * 60) // 1 hour
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Integer>
STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS =
+ public static final FeatureConfiguration<Integer>
STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS =
PolarisConfiguration.<Integer>builder()
.key("STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS")
.description(
"How long to store storage credentials in the local cache. This
should be less than "
+ STORAGE_CREDENTIAL_DURATION_SECONDS.key)
.defaultValue(30 * 60) // 30 minutes
- .build();
+ .buildFeatureConfiguration();
- public static final PolarisConfiguration<Integer>
MAX_METADATA_REFRESH_RETRIES =
+ public static final FeatureConfiguration<Integer>
MAX_METADATA_REFRESH_RETRIES =
PolarisConfiguration.<Integer>builder()
.key("MAX_METADATA_REFRESH_RETRIES")
.description(
"How many times to retry refreshing metadata when the previous
error was retryable")
.defaultValue(2)
- .build();
+ .buildFeatureConfiguration();
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
new file mode 100644
index 000000000..bcb380988
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.core.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.apache.polaris.core.context.CallContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An ABC for Polaris configurations that alter the service's behavior
+ *
+ * @param <T> The type of the configuration
+ */
+public abstract class PolarisConfiguration<T> {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(PolarisConfiguration.class);
+
+ public final String key;
+ public final String description;
+ public final T defaultValue;
+ private final Optional<String> catalogConfigImpl;
+ private final Class<T> typ;
+
+ @SuppressWarnings("unchecked")
+ protected PolarisConfiguration(
+ String key, String description, T defaultValue, Optional<String>
catalogConfig) {
+ this.key = key;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.catalogConfigImpl = catalogConfig;
+ this.typ = (Class<T>) defaultValue.getClass();
+ }
+
+ public boolean hasCatalogConfig() {
+ return catalogConfigImpl.isPresent();
+ }
+
+ public String catalogConfig() {
+ return catalogConfigImpl.orElseThrow(
+ () ->
+ new IllegalStateException(
+ "Attempted to read a catalog config key from a configuration
that doesn't have one."));
+ }
+
+ T cast(Object value) {
+ return this.typ.cast(value);
+ }
+
+ public static class Builder<T> {
+ private String key;
+ private String description;
+ private T defaultValue;
+ private Optional<String> catalogConfig = Optional.empty();
+
+ public Builder<T> key(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder<T> description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Builder<T> defaultValue(T defaultValue) {
+ if (defaultValue instanceof List<?>) {
+ // Type-safe handling of List
+ this.defaultValue = (T) new ArrayList<>((List<?>) defaultValue);
+ } else {
+ this.defaultValue = defaultValue;
+ }
+ return this;
+ }
+
+ public Builder<T> catalogConfig(String catalogConfig) {
+ this.catalogConfig = Optional.of(catalogConfig);
+ return this;
+ }
+
+ public FeatureConfiguration<T> buildFeatureConfiguration() {
+ if (key == null || description == null || defaultValue == null) {
+ throw new IllegalArgumentException("key, description, and defaultValue
are required");
+ }
+ return new FeatureConfiguration<>(key, description, defaultValue,
catalogConfig);
+ }
+
+ public BehaviorChangeConfiguration<T> buildBehaviorChangeConfiguration() {
+ if (key == null || description == null || defaultValue == null) {
+ throw new IllegalArgumentException("key, description, and defaultValue
are required");
+ }
+ if (catalogConfig.isPresent()) {
+ throw new IllegalArgumentException(
+ "catalogConfig is not valid for behavior change configs");
+ }
+ return new BehaviorChangeConfiguration<>(key, description, defaultValue,
catalogConfig);
+ }
+ }
+
+ /**
+ * Returns the value of a `PolarisConfiguration`, or the default if it
cannot be loaded. This
+ * method does not need to be used when a `CallContext` is already available
+ */
+ public static <T> T loadConfig(PolarisConfiguration<T> configuration) {
+ var callContext = CallContext.getCurrentContext();
+ if (callContext == null) {
+ LOGGER.warn(
+ String.format(
+ "Unable to load current call context; using %s = %s",
+ configuration.key, configuration.defaultValue));
+ return configuration.defaultValue;
+ }
+ return callContext
+ .getPolarisCallContext()
+ .getConfigurationStore()
+ .getConfiguration(callContext.getPolarisCallContext(), configuration);
+ }
+
+ public static <T> Builder<T> builder() {
+ return new Builder<>();
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/PolarisConfigurationStore.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfigurationStore.java
similarity index 98%
rename from
polaris-core/src/main/java/org/apache/polaris/core/PolarisConfigurationStore.java
rename to
polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfigurationStore.java
index a3c902206..65e613351 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/PolarisConfigurationStore.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfigurationStore.java
@@ -16,13 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.core;
+package org.apache.polaris.core.config;
import com.google.common.base.Preconditions;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
+import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.entity.CatalogEntity;
/**
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageConfigurationInfo.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageConfigurationInfo.java
index 6b0638e83..9631d95b4 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageConfigurationInfo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageConfigurationInfo.java
@@ -36,9 +36,9 @@ import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.admin.model.Catalog;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.PolarisEntity;
@@ -168,7 +168,7 @@ public abstract class PolarisStorageConfigurationInfo {
.getConfiguration(
CallContext.getCurrentContext().getPolarisCallContext(),
catalog,
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION);
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION);
if (!allowEscape
&& catalog.getCatalogType() != Catalog.TypeEnum.EXTERNAL
&& baseLocation != null) {
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
index 591a67f15..9b1c64900 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
@@ -18,8 +18,8 @@
*/
package org.apache.polaris.core.storage.aws;
-import static
org.apache.polaris.core.PolarisConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS;
-import static org.apache.polaris.core.PolarisConfiguration.loadConfig;
+import static
org.apache.polaris.core.config.FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS;
+import static org.apache.polaris.core.config.PolarisConfiguration.loadConfig;
import jakarta.annotation.Nonnull;
import java.net.URI;
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
index 4013cbd81..62e4fc4dc 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
@@ -45,8 +45,8 @@ import java.util.EnumMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.storage.InMemoryStorageIntegration;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.slf4j.Logger;
@@ -126,7 +126,7 @@ public class AzureCredentialsStorageIntegration
// clock skew between the client and server,
OffsetDateTime startTime =
start.truncatedTo(ChronoUnit.SECONDS).atOffset(ZoneOffset.UTC);
int intendedDurationSeconds =
-
PolarisConfiguration.loadConfig(PolarisConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
+
FeatureConfiguration.loadConfig(FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
OffsetDateTime intendedEndTime =
start.plusSeconds(intendedDurationSeconds).atOffset(ZoneOffset.UTC);
OffsetDateTime maxAllowedEndTime =
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
index bfc75214c..a80b38aa4 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
@@ -29,7 +29,8 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.apache.iceberg.exceptions.UnprocessableEntityException;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfiguration;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
@@ -96,15 +97,15 @@ public class StorageCredentialCache {
private static long maxCacheDurationMs() {
var cacheDurationSeconds =
PolarisConfiguration.loadConfig(
- PolarisConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS);
+ FeatureConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS);
var credentialDurationSeconds =
-
PolarisConfiguration.loadConfig(PolarisConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
+
PolarisConfiguration.loadConfig(FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
if (cacheDurationSeconds >= credentialDurationSeconds) {
throw new IllegalArgumentException(
String.format(
"%s should be less than %s",
-
PolarisConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS.key,
- PolarisConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS.key));
+
FeatureConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS.key,
+ FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS.key));
} else {
return cacheDurationSeconds * 1000L;
}
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapAtomicOperationMetaStoreManagerTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapAtomicOperationMetaStoreManagerTest.java
index 42e61f7d4..274841c52 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapAtomicOperationMetaStoreManagerTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapAtomicOperationMetaStoreManagerTest.java
@@ -22,9 +22,9 @@ import static
org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RAND
import java.time.ZoneId;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import
org.apache.polaris.core.persistence.transactional.PolarisTreeMapMetaStoreSessionImpl;
import org.apache.polaris.core.persistence.transactional.PolarisTreeMapStore;
import org.mockito.Mockito;
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapMetaStoreManagerTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapMetaStoreManagerTest.java
index 65f8080d9..d7491e038 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapMetaStoreManagerTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisTreeMapMetaStoreManagerTest.java
@@ -22,9 +22,9 @@ import static
org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RAND
import java.time.ZoneId;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import
org.apache.polaris.core.persistence.transactional.PolarisMetaStoreManagerImpl;
import
org.apache.polaris.core.persistence.transactional.PolarisTreeMapMetaStoreSessionImpl;
import org.apache.polaris.core.persistence.transactional.PolarisTreeMapStore;
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
index aa5317e99..0f1f4f151 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
@@ -26,9 +26,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
import org.assertj.core.api.Assertions;
diff --git
a/polaris-core/src/test/java/org/apache/polaris/service/storage/PolarisConfigurationStoreTest.java
b/polaris-core/src/test/java/org/apache/polaris/service/storage/PolarisConfigurationStoreTest.java
index a0da2c5d8..6abb76b8a 100644
---
a/polaris-core/src/test/java/org/apache/polaris/service/storage/PolarisConfigurationStoreTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/service/storage/PolarisConfigurationStoreTest.java
@@ -20,9 +20,12 @@ package org.apache.polaris.service.storage;
import jakarta.annotation.Nullable;
import java.util.List;
+import java.util.function.Supplier;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.BehaviorChangeConfiguration;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -91,6 +94,50 @@ public class PolarisConfigurationStoreTest {
.key(key)
.description("")
.defaultValue(defaultValue)
- .build();
+ .buildFeatureConfiguration();
+ }
+
+ private static class PolarisConfigurationConsumer {
+
+ private final PolarisCallContext polarisCallContext;
+ private final PolarisConfigurationStore configurationStore;
+
+ public PolarisConfigurationConsumer(
+ PolarisCallContext polarisCallContext, PolarisConfigurationStore
configurationStore) {
+ this.polarisCallContext = polarisCallContext;
+ this.configurationStore = configurationStore;
+ }
+
+ public <T> T consumeConfiguration(
+ PolarisConfiguration<Boolean> config, Supplier<T> code, T defaultVal) {
+ if (configurationStore.getConfiguration(polarisCallContext, config)) {
+ return code.get();
+ }
+ return defaultVal;
+ }
+ }
+
+ @Test
+ public void testBehaviorAndFeatureConfigs() {
+ PolarisConfigurationConsumer consumer =
+ new PolarisConfigurationConsumer(null, new PolarisConfigurationStore()
{});
+
+ FeatureConfiguration<Boolean> featureConfig =
+ PolarisConfiguration.<Boolean>builder()
+ .key("example")
+ .description("example")
+ .defaultValue(true)
+ .buildFeatureConfiguration();
+
+ BehaviorChangeConfiguration<Boolean> behaviorChangeConfig =
+ PolarisConfiguration.<Boolean>builder()
+ .key("example")
+ .description("example")
+ .defaultValue(true)
+ .buildBehaviorChangeConfiguration();
+
+ consumer.consumeConfiguration(behaviorChangeConfig, () -> 21, 22);
+
+ consumer.consumeConfiguration(featureConfig, () -> 42, 43);
}
}
diff --git
a/quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java
b/quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java
index 6788742ba..07ad02362 100644
---
a/quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java
+++
b/quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java
@@ -25,9 +25,9 @@ import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import java.time.Clock;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
diff --git
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusBehaviorChangesConfiguration.java
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusBehaviorChangesConfiguration.java
new file mode 100644
index 000000000..852529a0c
--- /dev/null
+++
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusBehaviorChangesConfiguration.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.service.quarkus.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkus.runtime.annotations.StaticInitSafe;
+import io.smallrye.config.ConfigMapping;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.service.config.FeaturesConfiguration;
+
+@StaticInitSafe
+@ConfigMapping(prefix = "polaris.behavior-changes")
+// FIXME: this should extend FeatureConfiguration, but that causes conflicts
with
+// QuarkusFeaturesConfiguration
+public interface QuarkusBehaviorChangesConfiguration {
+
+ Map<String, String> defaults();
+
+ Map<String, ? extends FeaturesConfiguration.RealmOverrides> realmOverrides();
+
+ default Map<String, Object> parseDefaults(ObjectMapper objectMapper) {
+ return convertMap(objectMapper, defaults());
+ }
+
+ default Map<String, Map<String, Object>> parseRealmOverrides(ObjectMapper
objectMapper) {
+ Map<String, Map<String, Object>> m = new HashMap<>();
+ for (String realm : realmOverrides().keySet()) {
+ m.put(realm, convertMap(objectMapper,
realmOverrides().get(realm).overrides()));
+ }
+ return m;
+ }
+
+ private static Map<String, Object> convertMap(
+ ObjectMapper objectMapper, Map<String, String> properties) {
+ Map<String, Object> m = new HashMap<>();
+ for (String configName : properties.keySet()) {
+ String json = properties.get(configName);
+ try {
+ JsonNode node = objectMapper.readTree(json);
+ m.put(configName, configValue(node));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(
+ "Invalid JSON value for feature configuration: " + configName, e);
+ }
+ }
+ return m;
+ }
+
+ private static Object configValue(JsonNode node) {
+ return switch (node.getNodeType()) {
+ case BOOLEAN -> node.asBoolean();
+ case STRING -> node.asText();
+ case NUMBER ->
+ switch (node.numberType()) {
+ case INT, LONG -> node.asLong();
+ case FLOAT, DOUBLE -> node.asDouble();
+ default ->
+ throw new IllegalArgumentException("Unsupported number type: "
+ node.numberType());
+ };
+ case ARRAY -> {
+ List<Object> list = new ArrayList<>();
+ node.elements().forEachRemaining(n -> list.add(configValue(n)));
+ yield List.copyOf(list);
+ }
+ default ->
+ throw new IllegalArgumentException(
+ "Unsupported feature configuration JSON type: " +
node.getNodeType());
+ };
+ }
+}
diff --git
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFeaturesConfiguration.java
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFeaturesConfiguration.java
index 76ba04968..85858440a 100644
---
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFeaturesConfiguration.java
+++
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusFeaturesConfiguration.java
@@ -20,7 +20,6 @@ package org.apache.polaris.service.quarkus.config;
import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.ConfigMapping;
-import io.smallrye.config.WithParentName;
import java.util.Map;
import org.apache.polaris.service.config.FeaturesConfiguration;
@@ -32,11 +31,5 @@ public interface QuarkusFeaturesConfiguration extends
FeaturesConfiguration {
Map<String, String> defaults();
@Override
- Map<String, QuarkusRealmOverrides> realmOverrides();
-
- interface QuarkusRealmOverrides extends RealmOverrides {
- @WithParentName
- @Override
- Map<String, String> overrides();
- }
+ Map<String, FeaturesConfiguration.RealmOverrides> realmOverrides();
}
diff --git
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java
index 6ec24ac42..c017c6135 100644
---
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java
+++
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java
@@ -33,12 +33,12 @@ import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.Context;
import java.time.Clock;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisAuthorizerImpl;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java
index 2b28e9f7f..de0e03fa8 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java
@@ -47,8 +47,6 @@ import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.iceberg.types.Types;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
@@ -57,6 +55,8 @@ import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisAuthorizerImpl;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.CatalogEntity;
@@ -164,7 +164,7 @@ public abstract class PolarisAuthzTestBase {
new PolarisAuthorizerImpl(
new DefaultConfigurationStore(
Map.of(
-
PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING.key,
+
FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING.key,
true)));
@Inject protected MetaStoreManagerFactory managerFactory;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisOverlappingTableTest.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisOverlappingTableTest.java
index 878c35e82..f2e929484 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisOverlappingTableTest.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisOverlappingTableTest.java
@@ -18,8 +18,8 @@
*/
package org.apache.polaris.service.quarkus.admin;
-import static
org.apache.polaris.core.PolarisConfiguration.ALLOW_TABLE_LOCATION_OVERLAP;
-import static
org.apache.polaris.core.PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION;
+import static
org.apache.polaris.core.config.FeatureConfiguration.ALLOW_TABLE_LOCATION_OVERLAP;
+import static
org.apache.polaris.core.config.FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION;
import static
org.apache.polaris.service.quarkus.admin.PolarisAuthzTestBase.SCHEMA;
import static org.assertj.core.api.Assertions.assertThat;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
index fbf6db504..29401e266 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
@@ -72,13 +72,13 @@ import org.apache.iceberg.inmemory.InMemoryFileIO;
import org.apache.iceberg.io.FileIO;
import org.apache.iceberg.types.Types;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizerImpl;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.CatalogEntity;
@@ -258,9 +258,9 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
.setDefaultBaseLocation(storageLocation)
.setReplaceNewLocationPrefixWithCatalogDefault("file:")
.addProperty(
-
PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.setStorageConfigurationInfo(storageConfigModel,
storageLocation)
.build());
@@ -686,9 +686,9 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
List.of(PolarisEntity.toCore(catalogEntity)),
new CatalogEntity.Builder(CatalogEntity.of(catalogEntity))
.addProperty(
-
PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
+
FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.build());
BasePolarisCatalog catalog = catalog();
TableMetadata tableMetadata =
@@ -743,9 +743,9 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
List.of(PolarisEntity.toCore(catalogEntity)),
new CatalogEntity.Builder(CatalogEntity.of(catalogEntity))
.addProperty(
-
PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
+
FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.build());
BasePolarisCatalog catalog = catalog();
TableMetadata tableMetadata =
@@ -797,9 +797,9 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
List.of(PolarisEntity.toCore(catalogEntity)),
new CatalogEntity.Builder(CatalogEntity.of(catalogEntity))
.addProperty(
-
PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
+
FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "false")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.build());
BasePolarisCatalog catalog = catalog();
InMemoryFileIO fileIO = getInMemoryIo(catalog);
@@ -908,7 +908,7 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
PolarisCallContext polarisCallContext =
callContext.getPolarisCallContext();
if (!polarisCallContext
.getConfigurationStore()
- .getConfiguration(polarisCallContext,
PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
+ .getConfiguration(polarisCallContext,
FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
.contains("FILE")) {
Assertions.assertThatThrownBy(() -> catalog.sendNotification(table,
request))
.isInstanceOf(ForbiddenException.class)
@@ -974,7 +974,7 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
PolarisCallContext polarisCallContext =
callContext.getPolarisCallContext();
if (!polarisCallContext
.getConfigurationStore()
- .getConfiguration(polarisCallContext,
PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
+ .getConfiguration(polarisCallContext,
FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
.contains("FILE")) {
Assertions.assertThatThrownBy(() -> catalog.sendNotification(table,
request))
.isInstanceOf(ForbiddenException.class)
@@ -995,7 +995,7 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
if (!polarisCallContext
.getConfigurationStore()
- .getConfiguration(polarisCallContext,
PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
+ .getConfiguration(polarisCallContext,
FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES)
.contains("FILE")) {
Assertions.assertThatThrownBy(() -> catalog.sendNotification(table,
newRequest))
.isInstanceOf(ForbiddenException.class)
@@ -1471,10 +1471,10 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
.setName(noPurgeCatalogName)
.setDefaultBaseLocation(storageLocation)
.setReplaceNewLocationPrefixWithCatalogDefault("file:")
-
.addProperty(PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
+
.addProperty(FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
-
.addProperty(PolarisConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(),
"false")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
.addProperty(FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(),
"false")
.setStorageConfigurationInfo(noPurgeStorageConfigModel,
storageLocation)
.build());
PolarisPassthroughResolutionView passthroughView =
@@ -1511,7 +1511,7 @@ public abstract class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCat
// Attempt to drop the table:
Assertions.assertThatThrownBy(() -> noPurgeCatalog.dropTable(TABLE, true))
.isInstanceOf(ForbiddenException.class)
-
.hasMessageContaining(PolarisConfiguration.DROP_WITH_PURGE_ENABLED.key);
+
.hasMessageContaining(FeatureConfiguration.DROP_WITH_PURGE_ENABLED.key);
}
private TableMetadata createSampleTableMetadata(String tableLocation) {
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogViewTest.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogViewTest.java
index b85443faf..471afa2d9 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogViewTest.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogViewTest.java
@@ -39,13 +39,13 @@ import org.apache.iceberg.CatalogProperties;
import org.apache.iceberg.catalog.Catalog;
import org.apache.iceberg.view.ViewCatalogTests;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizerImpl;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.CatalogEntity;
@@ -171,9 +171,9 @@ public class BasePolarisCatalogViewTest extends
ViewCatalogTests<BasePolarisCata
adminService.createCatalog(
new CatalogEntity.Builder()
.setName(CATALOG_NAME)
-
.addProperty(PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
+
.addProperty(FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
"true")
.addProperty(
-
PolarisConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
+
FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
.setDefaultBaseLocation("file://tmp")
.setStorageConfigurationInfo(
new FileStorageConfigInfo(
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
index 29d0bd90c..a17091cba 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
@@ -50,11 +50,11 @@ import
org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.requests.UpdateTableRequest;
import org.apache.iceberg.view.ImmutableSQLViewRepresentation;
import org.apache.iceberg.view.ImmutableViewVersion;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.CatalogEntity;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestHelper.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestHelper.java
index f7f5ed719..714929154 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestHelper.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestHelper.java
@@ -22,8 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.time.Clock;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.service.context.RealmContextResolver;
import org.junit.jupiter.api.TestInfo;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
index 21d30c1d8..0eafc2765 100644
---
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
+++
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
@@ -45,7 +45,6 @@ import org.apache.iceberg.exceptions.NoSuchViewException;
import org.apache.iceberg.exceptions.NotFoundException;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.admin.model.CatalogGrant;
import org.apache.polaris.core.admin.model.CatalogPrivilege;
@@ -66,6 +65,7 @@ import
org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.CatalogRoleEntity;
@@ -528,7 +528,7 @@ public class PolarisAdminService {
getCurrentPolarisContext()
.getConfigurationStore()
.getConfiguration(
- getCurrentPolarisContext(),
PolarisConfiguration.ALLOW_OVERLAPPING_CATALOG_URLS);
+ getCurrentPolarisContext(),
FeatureConfiguration.ALLOW_OVERLAPPING_CATALOG_URLS);
if (allowOverlappingCatalogUrls) {
return false;
@@ -598,7 +598,7 @@ public class PolarisAdminService {
boolean cleanup =
polarisCallContext
.getConfigurationStore()
- .getConfiguration(polarisCallContext,
PolarisConfiguration.CLEANUP_ON_CATALOG_DROP);
+ .getConfiguration(polarisCallContext,
FeatureConfiguration.CLEANUP_ON_CATALOG_DROP);
DropEntityResult dropEntityResult =
metaStoreManager.dropEntityIfExists(
getCurrentPolarisContext(), null, entity, Map.of(), cleanup);
diff --git
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
index 15fb3854d..8b92956f8 100644
---
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
+++
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
@@ -27,7 +27,6 @@ import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.NotAuthorizedException;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.admin.model.AddGrantRequest;
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogGrant;
@@ -58,6 +57,7 @@ import
org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
import org.apache.polaris.core.admin.model.ViewGrant;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.CatalogEntity;
@@ -137,7 +137,7 @@ public class PolarisServiceImpl
polarisCallContext
.getConfigurationStore()
.getConfiguration(
- polarisCallContext,
PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES);
+ polarisCallContext,
FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES);
if
(!allowedStorageTypes.contains(storageConfigInfo.getStorageType().name())) {
LOGGER
.atWarn()
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
index b49b73e9c..582394c37 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
@@ -77,9 +77,10 @@ import org.apache.iceberg.view.ViewMetadataParser;
import org.apache.iceberg.view.ViewOperations;
import org.apache.iceberg.view.ViewUtil;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfiguration;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
+import org.apache.polaris.core.config.BehaviorChangeConfiguration;
+import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.NamespaceEntity;
@@ -514,7 +515,7 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
- PolarisConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
+ FeatureConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
LOGGER.debug("Validating no overlap for {} with sibling tables or
namespaces", namespace);
validateNoLocationOverlap(
entity.getBaseLocation(), resolvedParent.getRawFullPath(),
entity.getName());
@@ -650,7 +651,7 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
polarisCallContext
.getConfigurationStore()
.getConfiguration(
- polarisCallContext,
PolarisConfiguration.CLEANUP_ON_NAMESPACE_DROP));
+ polarisCallContext,
FeatureConfiguration.CLEANUP_ON_NAMESPACE_DROP));
if (!dropEntityResult.isSuccess() &&
dropEntityResult.failedBecauseNotEmpty()) {
throw new NamespaceNotEmptyException("Namespace %s is not empty",
namespace);
@@ -680,7 +681,7 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
- PolarisConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
+ FeatureConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
LOGGER.debug("Validating no overlap with sibling tables or namespaces");
validateNoLocationOverlap(
NamespaceEntity.of(updatedEntity).getBaseLocation(),
@@ -976,7 +977,7 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
- PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES);
+ FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES);
if
(!allowedStorageTypes.contains(StorageConfigInfo.StorageTypeEnum.FILE.name())) {
List<String> invalidLocations =
locations.stream()
@@ -999,18 +1000,25 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
CatalogEntity catalog,
TableIdentifier identifier,
List<PolarisEntity> resolvedNamespace,
- String location) {
+ String location,
+ PolarisEntity entity) {
+ boolean validateViewOverlap =
+ callContext
+ .getPolarisCallContext()
+ .getConfigurationStore()
+ .getConfiguration(
+ callContext.getPolarisCallContext(),
+ BehaviorChangeConfiguration.VALIDATE_VIEW_LOCATION_OVERLAP);
+
if (callContext
.getPolarisCallContext()
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
catalog,
- PolarisConfiguration.ALLOW_TABLE_LOCATION_OVERLAP)) {
+ FeatureConfiguration.ALLOW_TABLE_LOCATION_OVERLAP)) {
LOGGER.debug("Skipping location overlap validation for identifier '{}'",
identifier);
- } else { // if (entity.getSubType().equals(PolarisEntitySubType.TABLE)) {
- // TODO - is this necessary for views? overlapping views do not expose
subdirectories via the
- // credential vending so this feels like an unnecessary restriction
+ } else if (validateViewOverlap ||
entity.getSubType().equals(PolarisEntitySubType.TABLE)) {
LOGGER.debug("Validating no overlap with sibling tables or namespaces");
validateNoLocationOverlap(location, resolvedNamespace,
identifier.name());
}
@@ -1288,7 +1296,11 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
dataLocations.forEach(
location ->
validateNoLocationOverlap(
- catalogEntity, tableIdentifier, resolvedNamespace,
location));
+ catalogEntity,
+ tableIdentifier,
+ resolvedNamespace,
+ location,
+ resolvedStorageEntity.getRawLeafEntity()));
// and that the metadata file points to a location within the table's
directory structure
if (metadata.metadataFileLocation() != null) {
validateMetadataFileInTableDir(tableIdentifier, metadata, catalog);
@@ -1373,12 +1385,12 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
polarisCallContext
.getConfigurationStore()
.getConfiguration(
- polarisCallContext,
PolarisConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION);
+ polarisCallContext,
FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION);
if (!allowEscape
&& !polarisCallContext
.getConfigurationStore()
.getConfiguration(
- polarisCallContext,
PolarisConfiguration.ALLOW_EXTERNAL_METADATA_FILE_LOCATION)) {
+ polarisCallContext,
FeatureConfiguration.ALLOW_EXTERNAL_METADATA_FILE_LOCATION)) {
LOGGER.debug(
"Validating base location {} for table {} in metadata file {}",
metadata.location(),
@@ -1487,7 +1499,11 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
// for the storage configuration inherited under this entity's path.
validateLocationForTableLike(identifier, metadata.location(),
resolvedStorageEntity);
validateNoLocationOverlap(
- catalogEntity, identifier, resolvedNamespace, metadata.location());
+ catalogEntity,
+ identifier,
+ resolvedNamespace,
+ metadata.location(),
+ resolvedStorageEntity.getRawLeafEntity());
}
Map<String, String> tableProperties = new
HashMap<>(metadata.properties());
@@ -1832,15 +1848,15 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
.getConfiguration(
callContext.getPolarisCallContext(),
catalogEntity,
- PolarisConfiguration.DROP_WITH_PURGE_ENABLED);
+ FeatureConfiguration.DROP_WITH_PURGE_ENABLED);
if (!dropWithPurgeEnabled) {
throw new ForbiddenException(
String.format(
"Unable to purge entity: %s. To enable this feature, set the
Polaris configuration %s "
+ "or the catalog configuration %s",
identifier.name(),
- PolarisConfiguration.DROP_WITH_PURGE_ENABLED.key,
- PolarisConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig()));
+ FeatureConfiguration.DROP_WITH_PURGE_ENABLED.key,
+ FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig()));
}
}
@@ -2071,6 +2087,6 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
.getPolarisCallContext()
.getConfigurationStore()
.getConfiguration(
- callContext.getPolarisCallContext(),
PolarisConfiguration.MAX_METADATA_REFRESH_RETRIES);
+ callContext.getPolarisCallContext(),
FeatureConfiguration.MAX_METADATA_REFRESH_RETRIES);
}
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java
index 968d9c5fd..eb199f0e9 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java
@@ -55,10 +55,10 @@ import
org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.responses.ConfigResponse;
import org.apache.iceberg.rest.responses.ImmutableLoadCredentialsResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisEntity;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java
index 489ccd488..837fb509f 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java
@@ -71,13 +71,13 @@ import org.apache.iceberg.rest.responses.ListTablesResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.rest.responses.LoadViewResponse;
import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.PolarisEntitySubType;
@@ -850,11 +850,11 @@ public class PolarisCatalogHandlerWrapper implements
AutoCloseable {
&& !configurationStore.getConfiguration(
callContext.getPolarisCallContext(),
catalogEntity,
- PolarisConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING)) {
+ FeatureConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING)) {
throw new ForbiddenException(
"Access Delegation is not enabled for this catalog. Please consult
applicable "
+ "documentation for the catalog config property '%s' to enable
this feature",
-
PolarisConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING.catalogConfig());
+
FeatureConfiguration.ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING.catalogConfig());
}
// TODO: Find a way for the configuration or caller to better express
whether to fail or omit
@@ -1070,7 +1070,7 @@ public class PolarisCatalogHandlerWrapper implements
AutoCloseable {
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
-
PolarisConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
+
FeatureConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
throw new BadRequestException(
"Unsupported operation: commitTransaction
containing SetLocation"
+ " for table '%s' and new location '%s'",
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
index 0a07a6097..31f2f9b03 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
@@ -31,7 +31,7 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.iceberg.CatalogUtil;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.io.FileIO;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisEntity;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/FileIOUtil.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/FileIOUtil.java
index e0bed634f..f70c594dd 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/FileIOUtil.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/FileIOUtil.java
@@ -22,8 +22,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.iceberg.catalog.TableIdentifier;
-import org.apache.polaris.core.PolarisConfiguration;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
@@ -88,8 +88,8 @@ public class FileIOUtil {
boolean skipCredentialSubscopingIndirection =
configurationStore.getConfiguration(
callContext.getPolarisCallContext(),
- PolarisConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.key,
-
PolarisConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.defaultValue);
+ FeatureConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.key,
+
FeatureConfiguration.SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION.defaultValue);
if (skipCredentialSubscopingIndirection) {
LOGGER
.atDebug()
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/WasbTranslatingFileIOFactory.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/WasbTranslatingFileIOFactory.java
index 3bb365368..a7074fdc0 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/WasbTranslatingFileIOFactory.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/WasbTranslatingFileIOFactory.java
@@ -26,7 +26,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.io.FileIO;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/config/DefaultConfigurationStore.java
b/service/common/src/main/java/org/apache/polaris/service/config/DefaultConfigurationStore.java
index 3db2fd998..8d9fd0e43 100644
---
a/service/common/src/main/java/org/apache/polaris/service/config/DefaultConfigurationStore.java
+++
b/service/common/src/main/java/org/apache/polaris/service/config/DefaultConfigurationStore.java
@@ -25,7 +25,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Map;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
@ApplicationScoped
diff --git
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultCallContextResolver.java
b/service/common/src/main/java/org/apache/polaris/service/context/DefaultCallContextResolver.java
index 14246692e..90f381d75 100644
---
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultCallContextResolver.java
+++
b/service/common/src/main/java/org/apache/polaris/service/context/DefaultCallContextResolver.java
@@ -24,8 +24,8 @@ import jakarta.inject.Inject;
import java.time.Clock;
import java.util.Map;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
diff --git
a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java
b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java
index 7dd4f6804..0f63106ac 100644
---
a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java
+++
b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java
@@ -29,10 +29,10 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisConfigurationStore;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisEntity;
diff --git
a/service/common/src/testFixtures/java/org/apache/polaris/service/catalog/io/MeasuredFileIOFactory.java
b/service/common/src/testFixtures/java/org/apache/polaris/service/catalog/io/MeasuredFileIOFactory.java
index 3e831361f..abfb6e8c3 100644
---
a/service/common/src/testFixtures/java/org/apache/polaris/service/catalog/io/MeasuredFileIOFactory.java
+++
b/service/common/src/testFixtures/java/org/apache/polaris/service/catalog/io/MeasuredFileIOFactory.java
@@ -29,7 +29,7 @@ import java.util.Set;
import java.util.function.Supplier;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.io.FileIO;
-import org.apache.polaris.core.PolarisConfigurationStore;
+import org.apache.polaris.core.config.PolarisConfigurationStore;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;