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 9415696dae..8e63a77dba 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.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 638e4b5821..0be4312c04 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 @@ /** * @implSpec This test expects the server to be configured with the following features configured: * * 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 e830a18ab4..d743351d9f 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 @@ * @implSpec @implSpec This test expects the server to be configured with the following features * configured: * */ 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 2a31f2f117..a832553cf8 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.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.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 void testCreateTableWithOverriddenBaseLocation() { Catalog catalog = managementApi.getCatalog(currentCatalogName); Map 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 void testCreateTableWithOverriddenBaseLocationCannotOverlapSibling() { Catalog catalog = managementApi.getCatalog(currentCatalogName); Map 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 void testCreateTableWithOverriddenBaseLocationMustResideInNsDirectory() { Catalog catalog = managementApi.getCatalog(currentCatalogName); Map 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 void testLoadTableWithAccessDelegationForExternalCatalogWithConfigDisable .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 1fb24f5fe8..28c38c99e0 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.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 @@ * 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 { @@ -99,9 +99,9 @@ public void before(TestInfo testInfo) { 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 3625c69e87..7333baa39a 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 @@ * @implSpec This test expects the server to be configured with the following features enabled: *
    *
  • {@link - * org.apache.polaris.core.PolarisConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}: + * org.apache.polaris.core.config.FeatureConfiguration#SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION}: * {@code true} - *
  • {@link org.apache.polaris.core.PolarisConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}: + *
  • {@link + * org.apache.polaris.core.config.FeatureConfiguration#ALLOW_OVERLAPPING_CATALOG_URLS}: * {@code true} *
*/ 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 3b5e962514..510cd0f6cd 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 @@ 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 96f449acbd..564be49dac 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.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 void authorizeOrThrow( 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 0000000000..a64c594028 --- /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 The type of the configuration + */ +public class BehaviorChangeConfiguration extends PolarisConfiguration { + + protected BehaviorChangeConfiguration( + String key, String description, T defaultValue, Optional catalogConfig) { + super(key, description, defaultValue, catalogConfig); + } + + public static final BehaviorChangeConfiguration VALIDATE_VIEW_LOCATION_OVERLAP = + PolarisConfiguration.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 ca1962e3c3..087d0525ee 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 { - - private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfiguration.class); - - public final String key; - public final String description; - public final T defaultValue; - private final Optional catalogConfigImpl; - private final Class 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 + */ +public class FeatureConfiguration extends PolarisConfiguration { + protected FeatureConfiguration( String key, String description, T defaultValue, Optional catalogConfig) { - this.key = key; - this.description = description; - this.defaultValue = defaultValue; - this.catalogConfigImpl = catalogConfig; - this.typ = (Class) 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 { - private String key; - private String description; - private T defaultValue; - private Optional catalogConfig = Optional.empty(); - - public Builder key(String key) { - this.key = key; - return this; - } - - public Builder description(String description) { - this.description = description; - return this; - } - - @SuppressWarnings("unchecked") - public Builder 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 catalogConfig(String catalogConfig) { - this.catalogConfig = Optional.of(catalogConfig); - return this; - } - - public PolarisConfiguration 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 loadConfig(PolarisConfiguration 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 Builder builder() { - return new Builder<>(); + super(key, description, defaultValue, catalogConfig); } - public static final PolarisConfiguration + public static final FeatureConfiguration ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING = PolarisConfiguration.builder() .key("ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING") @@ -132,9 +43,9 @@ public static Builder builder() { "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 SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION = + public static final FeatureConfiguration SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION = PolarisConfiguration.builder() .key("SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION") .description( @@ -147,9 +58,9 @@ public static Builder builder() { + " \"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 ALLOW_TABLE_LOCATION_OVERLAP = + public static final FeatureConfiguration ALLOW_TABLE_LOCATION_OVERLAP = PolarisConfiguration.builder() .key("ALLOW_TABLE_LOCATION_OVERLAP") .catalogConfig("allow.overlapping.table.location") @@ -157,58 +68,58 @@ public static Builder builder() { "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 ALLOW_NAMESPACE_LOCATION_OVERLAP = + public static final FeatureConfiguration ALLOW_NAMESPACE_LOCATION_OVERLAP = PolarisConfiguration.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 ALLOW_EXTERNAL_METADATA_FILE_LOCATION = + public static final FeatureConfiguration ALLOW_EXTERNAL_METADATA_FILE_LOCATION = PolarisConfiguration.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 ALLOW_OVERLAPPING_CATALOG_URLS = + public static final FeatureConfiguration ALLOW_OVERLAPPING_CATALOG_URLS = PolarisConfiguration.builder() .key("ALLOW_OVERLAPPING_CATALOG_URLS") .description("If set to true, allows catalog URLs to overlap.") .defaultValue(false) - .build(); + .buildFeatureConfiguration(); - public static final PolarisConfiguration ALLOW_UNSTRUCTURED_TABLE_LOCATION = + public static final FeatureConfiguration ALLOW_UNSTRUCTURED_TABLE_LOCATION = PolarisConfiguration.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 ALLOW_EXTERNAL_TABLE_LOCATION = + public static final FeatureConfiguration ALLOW_EXTERNAL_TABLE_LOCATION = PolarisConfiguration.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 ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING = + public static final FeatureConfiguration ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING = PolarisConfiguration.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> SUPPORTED_CATALOG_STORAGE_TYPES = + public static final FeatureConfiguration> SUPPORTED_CATALOG_STORAGE_TYPES = PolarisConfiguration.>builder() .key("SUPPORTED_CATALOG_STORAGE_TYPES") .catalogConfig("supported.storage.types") @@ -219,34 +130,34 @@ public static Builder builder() { StorageConfigInfo.StorageTypeEnum.AZURE.name(), StorageConfigInfo.StorageTypeEnum.GCS.name(), StorageConfigInfo.StorageTypeEnum.FILE.name())) - .build(); + .buildFeatureConfiguration(); - public static final PolarisConfiguration CLEANUP_ON_NAMESPACE_DROP = + public static final FeatureConfiguration CLEANUP_ON_NAMESPACE_DROP = PolarisConfiguration.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 CLEANUP_ON_CATALOG_DROP = + public static final FeatureConfiguration CLEANUP_ON_CATALOG_DROP = PolarisConfiguration.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 DROP_WITH_PURGE_ENABLED = + public static final FeatureConfiguration DROP_WITH_PURGE_ENABLED = PolarisConfiguration.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 STORAGE_CREDENTIAL_DURATION_SECONDS = + public static final FeatureConfiguration STORAGE_CREDENTIAL_DURATION_SECONDS = PolarisConfiguration.builder() .key("STORAGE_CREDENTIAL_DURATION_SECONDS") .description( @@ -254,22 +165,22 @@ public static Builder builder() { + " 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 STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS = + public static final FeatureConfiguration STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS = PolarisConfiguration.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 MAX_METADATA_REFRESH_RETRIES = + public static final FeatureConfiguration MAX_METADATA_REFRESH_RETRIES = PolarisConfiguration.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 0000000000..bcb3809881 --- /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 The type of the configuration + */ +public abstract class PolarisConfiguration { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfiguration.class); + + public final String key; + public final String description; + public final T defaultValue; + private final Optional catalogConfigImpl; + private final Class typ; + + @SuppressWarnings("unchecked") + protected PolarisConfiguration( + String key, String description, T defaultValue, Optional catalogConfig) { + this.key = key; + this.description = description; + this.defaultValue = defaultValue; + this.catalogConfigImpl = catalogConfig; + this.typ = (Class) 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 { + private String key; + private String description; + private T defaultValue; + private Optional catalogConfig = Optional.empty(); + + public Builder key(String key) { + this.key = key; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + @SuppressWarnings("unchecked") + public Builder 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 catalogConfig(String catalogConfig) { + this.catalogConfig = Optional.of(catalogConfig); + return this; + } + + public FeatureConfiguration 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 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 loadConfig(PolarisConfiguration 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 Builder 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 a3c9022060..65e6133515 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 6b0638e837..9631d95b42 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.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 static Optional forEntityPath( .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 591a67f15d..9b1c64900b 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 4013cbd81a..62e4fc4dc1 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.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 EnumMap getSubscopedCreds( // 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 bfc75214c6..a80b38aa45 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.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 long expireAfterRead( 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 42e61f7d48..274841c52a 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 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 65f8080d91..d7491e0384 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 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 aa5317e998..0f1f4f151b 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.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 a0da2c5d8f..6abb76b8ad 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 @@ 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 @@ private static PolarisConfiguration buildConfig(String key, T defaultValu .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 consumeConfiguration( + PolarisConfiguration config, Supplier 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 featureConfig = + PolarisConfiguration.builder() + .key("example") + .description("example") + .defaultValue(true) + .buildFeatureConfiguration(); + + BehaviorChangeConfiguration behaviorChangeConfig = + PolarisConfiguration.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 6788742ba8..07ad023629 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.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 0000000000..852529a0cc --- /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 defaults(); + + Map realmOverrides(); + + default Map parseDefaults(ObjectMapper objectMapper) { + return convertMap(objectMapper, defaults()); + } + + default Map> parseRealmOverrides(ObjectMapper objectMapper) { + Map> m = new HashMap<>(); + for (String realm : realmOverrides().keySet()) { + m.put(realm, convertMap(objectMapper, realmOverrides().get(realm).overrides())); + } + return m; + } + + private static Map convertMap( + ObjectMapper objectMapper, Map properties) { + Map 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 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 76ba049681..85858440a3 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 @@ 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 defaults(); @Override - Map realmOverrides(); - - interface QuarkusRealmOverrides extends RealmOverrides { - @WithParentName - @Override - Map overrides(); - } + Map 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 6ec24ac422..c017c61354 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.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 2b28e9f7f6..de0e03fa8d 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.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.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 Map getConfigOverrides() { 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 878c35e829..f2e9294840 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 fbf6db5047..29401e2669 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.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 void before(TestInfo testInfo) { .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 void testCreateNotificationCreateTableInExternalLocation() { 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 void testCreateNotificationCreateTableOutsideOfMetadataLocation() { 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 void testUpdateNotificationCreateTableInExternalLocation() { 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 void testUpdateNotificationCreateTableWithLocalFilePrefix() { 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 void testUpdateNotificationCreateTableWithHttpPrefix() { 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 void testUpdateNotificationCreateTableWithHttpPrefix() { 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 void testDropTableWithPurgeDisabled() { .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 void testDropTableWithPurgeDisabled() { // 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 b85443faf2..471afa2d9f 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.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 void before(TestInfo testInfo) { 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 29d0bd90c1..a17091cba2 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.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 f7f5ed7193..7149291543 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 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 21d30c1d8b..0eafc27659 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.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.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 @@ private boolean catalogOverlapsWithExistingCatalog(CatalogEntity catalogEntity) getCurrentPolarisContext() .getConfigurationStore() .getConfiguration( - getCurrentPolarisContext(), PolarisConfiguration.ALLOW_OVERLAPPING_CATALOG_URLS); + getCurrentPolarisContext(), FeatureConfiguration.ALLOW_OVERLAPPING_CATALOG_URLS); if (allowOverlappingCatalogUrls) { return false; @@ -598,7 +598,7 @@ public void deleteCatalog(String name) { 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 15fb3854d7..8b92956f8e 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.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.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 @@ private void validateStorageConfig(StorageConfigInfo storageConfigInfo) { 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 b49b73e9cc..582394c373 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.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 @@ private void createNamespaceInternal( .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 boolean dropNamespace(Namespace namespace) throws NamespaceNotEmptyExcept 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 boolean setProperties(Namespace namespace, Map properties .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 @@ private void validateLocationsForTableLike( .getConfigurationStore() .getConfiguration( callContext.getPolarisCallContext(), - PolarisConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES); + FeatureConfiguration.SUPPORTED_CATALOG_STORAGE_TYPES); if (!allowedStorageTypes.contains(StorageConfigInfo.StorageTypeEnum.FILE.name())) { List invalidLocations = locations.stream() @@ -999,18 +1000,25 @@ private void validateNoLocationOverlap( CatalogEntity catalog, TableIdentifier identifier, List 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 void doCommit(TableMetadata base, TableMetadata metadata) { 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 @@ private void validateMetadataFileInTableDir( 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 void doCommit(ViewMetadata base, ViewMetadata metadata) { // 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 tableProperties = new HashMap<>(metadata.properties()); @@ -1832,15 +1848,15 @@ private void updateTableLike(TableIdentifier identifier, PolarisEntity entity) { .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 @@ private int getMaxMetadataRefreshRetries() { .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 968d9c5fda..eb199f0e97 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.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 489ccd488a..837fb509f7 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.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 LoadTableResponse loadTableWithAccessDelegation( && !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 void commitTransaction(CommitTransactionRequest commitTransactionRequest) .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 0a07a60970..31f2f9b03a 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.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 e0bed634ff..f70c594ddf 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.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 static Map refreshCredentials( 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 3bb365368d..a7074fdc00 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.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 3db2fd9984..8d9fd0e430 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.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 14246692e3..90f381d759 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 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 7dd4f68045..0f63106ac2 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.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 3e831361ff..abfb6e8c30 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.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;