From 136e381dafb15e48132ae71f8b42b96ce88daf71 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Mon, 9 Mar 2026 23:58:24 +0530 Subject: [PATCH 01/17] Introduce DataSourceResolver for multi-datasource support in JDBC persistence This commit introduces the DataSourceResolver interface and a DefaultDataSourceResolver implementation to enable flexible routing of DataSources based on realm and store type (e.g., main, metrics, events). Key changes: - New DataSourceResolver interface with resolve(realmId, storeType) and getAllUniqueDataSources() methods - DefaultDataSourceResolver that routes all requests to the default DataSource (backward-compatible no-op) - Refactored JdbcMetaStoreManagerFactory to use DataSourceResolver instead of direct DataSource injection This lays the groundwork for isolating different persistence workloads (entity metadata vs metrics vs events) into separate connection pools or databases to mitigate noisy neighbor effects. Closes #3890 --- .../relational/jdbc/DataSourceResolver.java | 54 +++++++++ .../jdbc/DefaultDataSourceResolver.java | 67 +++++++++++ .../jdbc/JdbcMetaStoreManagerFactory.java | 107 ++++++++++-------- 3 files changed, 178 insertions(+), 50 deletions(-) create mode 100644 persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java create mode 100644 persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java new file mode 100644 index 0000000000..8c653931e1 --- /dev/null +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -0,0 +1,54 @@ +/* + * 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.persistence.relational.jdbc; + +import java.util.Set; +import javax.sql.DataSource; + +/** + * Service to resolve the correct {@link DataSource} for a given realm and store + * type. + * This enables isolating different workloads (e.g., entity metadata vs metrics + * vs events) + * into different physical databases or connection pools. + */ +public interface DataSourceResolver { + + String STORE_TYPE_MAIN = "main"; + String STORE_TYPE_METRICS = "metrics"; + String STORE_TYPE_EVENTS = "events"; + + /** + * Resolves the DataSource for a given realm and store type. + * + * @param realmId the realm identifier + * @param storeType the type of store (e.g., main, metrics, events) + * @return the resolved DataSource + */ + DataSource resolve(String realmId, String storeType); + + /** + * Returns all unique DataSources managed by this resolver. + * This is useful for global operations like schema initialization against all + * data sources. + * + * @return a set of all DataSources + */ + Set getAllUniqueDataSources(); +} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java new file mode 100644 index 0000000000..4739f861a3 --- /dev/null +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java @@ -0,0 +1,67 @@ +/* + * 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.persistence.relational.jdbc; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import java.util.HashSet; +import java.util.Set; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default implementation of {@link DataSourceResolver} that routes all realms + * and store types to a + * single default {@link DataSource}. This serves as both the production default + * and the base for + * multi-datasource extensions. + * + *

+ * To enable per-realm or per-store datasource routing, this class can be + * extended or replaced + * with a custom implementation that resolves named datasources based on + * configuration. + */ +@ApplicationScoped +public class DefaultDataSourceResolver implements DataSourceResolver { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); + + private final Instance defaultDataSource; + + @Inject + public DefaultDataSourceResolver(Instance defaultDataSource) { + this.defaultDataSource = defaultDataSource; + } + + @Override + public DataSource resolve(String realmId, String storeType) { + LOGGER.debug("Using default DataSource for realm '{}' and store '{}'", realmId, storeType); + return defaultDataSource.get(); + } + + @Override + public Set getAllUniqueDataSources() { + Set dataSources = new HashSet<>(); + dataSources.add(defaultDataSource.get()); + return dataSources; + } +} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 032934bae1..85edf4c29c 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -57,7 +57,8 @@ import org.slf4j.LoggerFactory; /** - * The implementation of Configuration interface for configuring the {@link PolarisMetaStoreManager} + * The implementation of Configuration interface for configuring the + * {@link PolarisMetaStoreManager} * using a JDBC backed by SQL metastore. TODO: refactor - ... */ @@ -71,14 +72,21 @@ public class JdbcMetaStoreManagerFactory implements MetaStoreManagerFactory { final Map entityCacheMap = new HashMap<>(); final Map> sessionSupplierMap = new HashMap<>(); - @Inject Clock clock; - @Inject PolarisDiagnostics diagnostics; - @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; - @Inject Instance dataSource; - @Inject RelationalJdbcConfiguration relationalJdbcConfiguration; - @Inject RealmConfig realmConfig; - - protected JdbcMetaStoreManagerFactory() {} + @Inject + Clock clock; + @Inject + PolarisDiagnostics diagnostics; + @Inject + PolarisStorageIntegrationProvider storageIntegrationProvider; + @Inject + Instance dataSourceResolver; + @Inject + RelationalJdbcConfiguration relationalJdbcConfiguration; + @Inject + RealmConfig realmConfig; + + protected JdbcMetaStoreManagerFactory() { + } protected PrincipalSecretsGenerator secretsGenerator( String realmId, @Nullable RootCredentialsSet rootCredentialsSet) { @@ -101,30 +109,29 @@ private void initializeForRealm( // RealmContext (request-scoped bean) can still create a JdbcBasePersistenceImpl String realmId = realmContext.getRealmIdentifier(); // determine schemaVersion once per realm - final int schemaVersion = - JdbcBasePersistenceImpl.loadSchemaVersion( - datasourceOperations, - realmConfig.getConfig(BehaviorChangeConfiguration.SCHEMA_VERSION_FALL_BACK_ON_DNE)); + final int schemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion( + datasourceOperations, + realmConfig.getConfig(BehaviorChangeConfiguration.SCHEMA_VERSION_FALL_BACK_ON_DNE)); sessionSupplierMap.put( realmId, - () -> - new JdbcBasePersistenceImpl( - diagnostics, - datasourceOperations, - secretsGenerator(realmId, rootCredentialsSet), - storageIntegrationProvider, - realmId, - schemaVersion)); + () -> new JdbcBasePersistenceImpl( + diagnostics, + datasourceOperations, + secretsGenerator(realmId, rootCredentialsSet), + storageIntegrationProvider, + realmId, + schemaVersion)); PolarisMetaStoreManager metaStoreManager = createNewMetaStoreManager(); metaStoreManagerMap.put(realmId, metaStoreManager); } - public DatasourceOperations getDatasourceOperations() { + public DatasourceOperations getDatasourceOperations(String realmId, String storeType) { DatasourceOperations databaseOperations; try { - databaseOperations = new DatasourceOperations(dataSource.get(), relationalJdbcConfiguration); + DataSource resolvedDs = dataSourceResolver.get().resolve(realmId, storeType); + databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); } @@ -136,12 +143,11 @@ public synchronized Map bootstrapRealms( Iterable realms, RootCredentialsSet rootCredentialsSet) { SchemaOptions schemaOptions = ImmutableSchemaOptions.builder().build(); - BootstrapOptions bootstrapOptions = - ImmutableBootstrapOptions.builder() - .realms(realms) - .rootCredentialsSet(rootCredentialsSet) - .schemaOptions(schemaOptions) - .build(); + BootstrapOptions bootstrapOptions = ImmutableBootstrapOptions.builder() + .realms(realms) + .rootCredentialsSet(rootCredentialsSet) + .schemaOptions(schemaOptions) + .build(); return bootstrapRealms(bootstrapOptions); } @@ -154,16 +160,14 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = getDatasourceOperations(); - int currentSchemaVersion = - JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); + DatasourceOperations datasourceOperations = getDatasourceOperations(realm, DataSourceResolver.STORE_TYPE_MAIN); + int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); - int effectiveSchemaVersion = - JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( - datasourceOperations.getDatabaseType(), - currentSchemaVersion, - requestedSchemaVersion, - JdbcBasePersistenceImpl.entityTableExists(datasourceOperations)); + int effectiveSchemaVersion = JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( + datasourceOperations.getDatabaseType(), + currentSchemaVersion, + requestedSchemaVersion, + JdbcBasePersistenceImpl.entityTableExists(datasourceOperations)); LOGGER.info( "Effective schema version: {} for bootstrapping realm: {}", effectiveSchemaVersion, @@ -181,13 +185,11 @@ public synchronized Map bootstrapRealms( initializeForRealm( datasourceOperations, realmContext, bootstrapOptions.rootCredentialsSet()); - PolarisMetaStoreManager metaStoreManager = - metaStoreManagerMap.get(realmContext.getRealmIdentifier()); + PolarisMetaStoreManager metaStoreManager = metaStoreManagerMap.get(realmContext.getRealmIdentifier()); BasePersistence metaStore = sessionSupplierMap.get(realmContext.getRealmIdentifier()).get(); PolarisCallContext polarisContext = new PolarisCallContext(realmContext, metaStore); - PrincipalSecretsResult secretsResult = - createPolarisPrincipalForRealm(metaStoreManager, polarisContext); + PrincipalSecretsResult secretsResult = createPolarisPrincipalForRealm(metaStoreManager, polarisContext); results.put(realm, secretsResult); } } @@ -219,7 +221,8 @@ public Map purgeRealms(Iterable realms) { public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( RealmContext realmContext) { if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations(); + DatasourceOperations datasourceOperations = getDatasourceOperations( + realmContext.getRealmIdentifier(), DataSourceResolver.STORE_TYPE_MAIN); initializeForRealm(datasourceOperations, realmContext, null); checkPolarisServiceBootstrappedForRealm(realmContext); } @@ -229,7 +232,8 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( @Override public synchronized BasePersistence getOrCreateSession(RealmContext realmContext) { if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations(); + DatasourceOperations datasourceOperations = getDatasourceOperations( + realmContext.getRealmIdentifier(), DataSourceResolver.STORE_TYPE_MAIN); initializeForRealm(datasourceOperations, realmContext, null); } checkPolarisServiceBootstrappedForRealm(realmContext); @@ -250,15 +254,18 @@ public synchronized EntityCache getOrCreateEntityCache( } /** - * In this method we check if Service was bootstrapped for a given realm, i.e. that all the - * entities were created (root principal, root principal role, etc) If service was not - * bootstrapped we are throwing IllegalStateException exception That will cause service to crash - * and force user to run Bootstrap command and initialize MetaStore and create all the required + * In this method we check if Service was bootstrapped for a given realm, i.e. + * that all the + * entities were created (root principal, root principal role, etc) If service + * was not + * bootstrapped we are throwing IllegalStateException exception That will cause + * service to crash + * and force user to run Bootstrap command and initialize MetaStore and create + * all the required * entities */ private void checkPolarisServiceBootstrappedForRealm(RealmContext realmContext) { - PolarisMetaStoreManager metaStoreManager = - metaStoreManagerMap.get(realmContext.getRealmIdentifier()); + PolarisMetaStoreManager metaStoreManager = metaStoreManagerMap.get(realmContext.getRealmIdentifier()); BasePersistence metaStore = sessionSupplierMap.get(realmContext.getRealmIdentifier()).get(); PolarisCallContext polarisContext = new PolarisCallContext(realmContext, metaStore); From 207aa4c8c547b61e834ab5a00010b2892b74f129 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 10 Mar 2026 00:26:03 +0530 Subject: [PATCH 02/17] Address review comments on DataSourceResolver PR - Refactored StoreType to an enum in DataSourceResolver - Removed getAllUniqueDataSources() from DataSourceResolver (Admin use case) - Moved DefaultDataSourceResolver to runtime/common/config/jdbc - Inject plain DataSource instead of Instance in DefaultDataSourceResolver - Updated JdbcMetaStoreManagerFactory to use the new StoreType enum --- .../relational/jdbc/DataSourceResolver.java | 20 ++++------- .../jdbc/JdbcMetaStoreManagerFactory.java | 8 ++--- .../jdbc/DefaultDataSourceResolver.java | 33 +++++-------------- 3 files changed, 19 insertions(+), 42 deletions(-) rename {persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational => runtime/common/src/main/java/org/apache/polaris/quarkus/common/config}/jdbc/DefaultDataSourceResolver.java (60%) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 8c653931e1..45c6847fb2 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -30,9 +30,12 @@ */ public interface DataSourceResolver { - String STORE_TYPE_MAIN = "main"; - String STORE_TYPE_METRICS = "metrics"; - String STORE_TYPE_EVENTS = "events"; + /** The type of store representing the workload pattern. */ + enum StoreType { + MAIN, + METRICS, + EVENTS + } /** * Resolves the DataSource for a given realm and store type. @@ -41,14 +44,5 @@ public interface DataSourceResolver { * @param storeType the type of store (e.g., main, metrics, events) * @return the resolved DataSource */ - DataSource resolve(String realmId, String storeType); - - /** - * Returns all unique DataSources managed by this resolver. - * This is useful for global operations like schema initialization against all - * data sources. - * - * @return a set of all DataSources - */ - Set getAllUniqueDataSources(); + DataSource resolve(String realmId, StoreType storeType); } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 85edf4c29c..9773340f51 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -127,7 +127,7 @@ private void initializeForRealm( metaStoreManagerMap.put(realmId, metaStoreManager); } - public DatasourceOperations getDatasourceOperations(String realmId, String storeType) { + public DatasourceOperations getDatasourceOperations(String realmId, DataSourceResolver.StoreType storeType) { DatasourceOperations databaseOperations; try { DataSource resolvedDs = dataSourceResolver.get().resolve(realmId, storeType); @@ -160,7 +160,7 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = getDatasourceOperations(realm, DataSourceResolver.STORE_TYPE_MAIN); + DatasourceOperations datasourceOperations = getDatasourceOperations(realm, DataSourceResolver.StoreType.MAIN); int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); int effectiveSchemaVersion = JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( @@ -222,7 +222,7 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( RealmContext realmContext) { if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { DatasourceOperations datasourceOperations = getDatasourceOperations( - realmContext.getRealmIdentifier(), DataSourceResolver.STORE_TYPE_MAIN); + realmContext.getRealmIdentifier(), DataSourceResolver.StoreType.MAIN); initializeForRealm(datasourceOperations, realmContext, null); checkPolarisServiceBootstrappedForRealm(realmContext); } @@ -233,7 +233,7 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( public synchronized BasePersistence getOrCreateSession(RealmContext realmContext) { if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) { DatasourceOperations datasourceOperations = getDatasourceOperations( - realmContext.getRealmIdentifier(), DataSourceResolver.STORE_TYPE_MAIN); + realmContext.getRealmIdentifier(), DataSourceResolver.StoreType.MAIN); initializeForRealm(datasourceOperations, realmContext, null); } checkPolarisServiceBootstrappedForRealm(realmContext); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java similarity index 60% rename from persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java rename to runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java index 4739f861a3..e025206eb0 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java @@ -16,52 +16,35 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.persistence.relational.jdbc; +package org.apache.polaris.quarkus.common.config.jdbc; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; -import java.util.HashSet; -import java.util.Set; import javax.sql.DataSource; +import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation of {@link DataSourceResolver} that routes all realms - * and store types to a - * single default {@link DataSource}. This serves as both the production default - * and the base for - * multi-datasource extensions. - * - *

- * To enable per-realm or per-store datasource routing, this class can be - * extended or replaced - * with a custom implementation that resolves named datasources based on - * configuration. + * and store types to a single default {@link DataSource}. This serves as both + * the production default and the base for multi-datasource extensions. */ @ApplicationScoped public class DefaultDataSourceResolver implements DataSourceResolver { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); - private final Instance defaultDataSource; + private final DataSource defaultDataSource; @Inject - public DefaultDataSourceResolver(Instance defaultDataSource) { + public DefaultDataSourceResolver(DataSource defaultDataSource) { this.defaultDataSource = defaultDataSource; } @Override - public DataSource resolve(String realmId, String storeType) { + public DataSource resolve(String realmId, StoreType storeType) { LOGGER.debug("Using default DataSource for realm '{}' and store '{}'", realmId, storeType); - return defaultDataSource.get(); - } - - @Override - public Set getAllUniqueDataSources() { - Set dataSources = new HashSet<>(); - dataSources.add(defaultDataSource.get()); - return dataSources; + return defaultDataSource; } } From e024beab2ae7ff4ff8c2864481a5fa522d192509 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 10 Mar 2026 00:43:27 +0530 Subject: [PATCH 03/17] Final review nits on DataSourceResolver PR - Renamed StoreType.MAIN to METASTORE - Use RealmContext instead of String realmId in DataSourceResolver.resolve - Added missing polaris-core dependency to runtime-common - Fixed formatting of @Inject fields for Spotless --- .../relational/jdbc/DataSourceResolver.java | 38 +++--- .../jdbc/JdbcMetaStoreManagerFactory.java | 115 +++++++++--------- runtime/common/build.gradle.kts | 1 + .../jdbc/DefaultDataSourceResolver.java | 32 ++--- 4 files changed, 96 insertions(+), 90 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 45c6847fb2..6513511db5 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -18,31 +18,29 @@ */ package org.apache.polaris.persistence.relational.jdbc; -import java.util.Set; import javax.sql.DataSource; /** - * Service to resolve the correct {@link DataSource} for a given realm and store - * type. - * This enables isolating different workloads (e.g., entity metadata vs metrics - * vs events) - * into different physical databases or connection pools. + * Service to resolve the correct {@link DataSource} for a given realm and store type. This enables + * isolating different workloads (e.g., entity metadata vs metrics vs events) into different + * physical databases or connection pools. */ public interface DataSourceResolver { - /** The type of store representing the workload pattern. */ - enum StoreType { - MAIN, - METRICS, - EVENTS - } + /** The type of store representing the workload pattern. */ + enum StoreType { + METASTORE, + METRICS, + EVENTS + } - /** - * Resolves the DataSource for a given realm and store type. - * - * @param realmId the realm identifier - * @param storeType the type of store (e.g., main, metrics, events) - * @return the resolved DataSource - */ - DataSource resolve(String realmId, StoreType storeType); + /** + * Resolves the DataSource for a given realm and store type. + * + * @param realmId the realm identifier + * @param storeType the type of store (e.g., main, metrics, events) + * @return the resolved DataSource + */ + DataSource resolve( + org.apache.polaris.core.context.RealmContext realmContext, StoreType storeType); } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 9773340f51..3311c6f124 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -57,8 +57,7 @@ import org.slf4j.LoggerFactory; /** - * The implementation of Configuration interface for configuring the - * {@link PolarisMetaStoreManager} + * The implementation of Configuration interface for configuring the {@link PolarisMetaStoreManager} * using a JDBC backed by SQL metastore. TODO: refactor - ... */ @@ -72,21 +71,19 @@ public class JdbcMetaStoreManagerFactory implements MetaStoreManagerFactory { final Map entityCacheMap = new HashMap<>(); final Map> sessionSupplierMap = new HashMap<>(); - @Inject - Clock clock; - @Inject - PolarisDiagnostics diagnostics; - @Inject - PolarisStorageIntegrationProvider storageIntegrationProvider; - @Inject - Instance dataSourceResolver; - @Inject - RelationalJdbcConfiguration relationalJdbcConfiguration; - @Inject - RealmConfig realmConfig; - - protected JdbcMetaStoreManagerFactory() { - } + @Inject Clock clock; + + @Inject PolarisDiagnostics diagnostics; + + @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; + + @Inject Instance dataSourceResolver; + + @Inject RelationalJdbcConfiguration relationalJdbcConfiguration; + + @Inject RealmConfig realmConfig; + + protected JdbcMetaStoreManagerFactory() {} protected PrincipalSecretsGenerator secretsGenerator( String realmId, @Nullable RootCredentialsSet rootCredentialsSet) { @@ -109,28 +106,31 @@ private void initializeForRealm( // RealmContext (request-scoped bean) can still create a JdbcBasePersistenceImpl String realmId = realmContext.getRealmIdentifier(); // determine schemaVersion once per realm - final int schemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion( - datasourceOperations, - realmConfig.getConfig(BehaviorChangeConfiguration.SCHEMA_VERSION_FALL_BACK_ON_DNE)); + final int schemaVersion = + JdbcBasePersistenceImpl.loadSchemaVersion( + datasourceOperations, + realmConfig.getConfig(BehaviorChangeConfiguration.SCHEMA_VERSION_FALL_BACK_ON_DNE)); sessionSupplierMap.put( realmId, - () -> new JdbcBasePersistenceImpl( - diagnostics, - datasourceOperations, - secretsGenerator(realmId, rootCredentialsSet), - storageIntegrationProvider, - realmId, - schemaVersion)); + () -> + new JdbcBasePersistenceImpl( + diagnostics, + datasourceOperations, + secretsGenerator(realmId, rootCredentialsSet), + storageIntegrationProvider, + realmId, + schemaVersion)); PolarisMetaStoreManager metaStoreManager = createNewMetaStoreManager(); metaStoreManagerMap.put(realmId, metaStoreManager); } - public DatasourceOperations getDatasourceOperations(String realmId, DataSourceResolver.StoreType storeType) { + public DatasourceOperations getDatasourceOperations( + RealmContext realmContext, DataSourceResolver.StoreType storeType) { DatasourceOperations databaseOperations; try { - DataSource resolvedDs = dataSourceResolver.get().resolve(realmId, storeType); + DataSource resolvedDs = dataSourceResolver.get().resolve(realmContext, storeType); databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); @@ -143,11 +143,12 @@ public synchronized Map bootstrapRealms( Iterable realms, RootCredentialsSet rootCredentialsSet) { SchemaOptions schemaOptions = ImmutableSchemaOptions.builder().build(); - BootstrapOptions bootstrapOptions = ImmutableBootstrapOptions.builder() - .realms(realms) - .rootCredentialsSet(rootCredentialsSet) - .schemaOptions(schemaOptions) - .build(); + BootstrapOptions bootstrapOptions = + ImmutableBootstrapOptions.builder() + .realms(realms) + .rootCredentialsSet(rootCredentialsSet) + .schemaOptions(schemaOptions) + .build(); return bootstrapRealms(bootstrapOptions); } @@ -160,14 +161,17 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = getDatasourceOperations(realm, DataSourceResolver.StoreType.MAIN); - int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); + int currentSchemaVersion = + JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); - int effectiveSchemaVersion = JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( - datasourceOperations.getDatabaseType(), - currentSchemaVersion, - requestedSchemaVersion, - JdbcBasePersistenceImpl.entityTableExists(datasourceOperations)); + int effectiveSchemaVersion = + JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( + datasourceOperations.getDatabaseType(), + currentSchemaVersion, + requestedSchemaVersion, + JdbcBasePersistenceImpl.entityTableExists(datasourceOperations)); LOGGER.info( "Effective schema version: {} for bootstrapping realm: {}", effectiveSchemaVersion, @@ -185,11 +189,13 @@ public synchronized Map bootstrapRealms( initializeForRealm( datasourceOperations, realmContext, bootstrapOptions.rootCredentialsSet()); - PolarisMetaStoreManager metaStoreManager = metaStoreManagerMap.get(realmContext.getRealmIdentifier()); + PolarisMetaStoreManager metaStoreManager = + metaStoreManagerMap.get(realmContext.getRealmIdentifier()); BasePersistence metaStore = sessionSupplierMap.get(realmContext.getRealmIdentifier()).get(); PolarisCallContext polarisContext = new PolarisCallContext(realmContext, metaStore); - PrincipalSecretsResult secretsResult = createPolarisPrincipalForRealm(metaStoreManager, polarisContext); + PrincipalSecretsResult secretsResult = + createPolarisPrincipalForRealm(metaStoreManager, polarisContext); results.put(realm, secretsResult); } } @@ -221,8 +227,8 @@ public Map purgeRealms(Iterable realms) { public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( RealmContext realmContext) { if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations( - realmContext.getRealmIdentifier(), DataSourceResolver.StoreType.MAIN); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); initializeForRealm(datasourceOperations, realmContext, null); checkPolarisServiceBootstrappedForRealm(realmContext); } @@ -232,8 +238,8 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( @Override public synchronized BasePersistence getOrCreateSession(RealmContext realmContext) { if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations( - realmContext.getRealmIdentifier(), DataSourceResolver.StoreType.MAIN); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); initializeForRealm(datasourceOperations, realmContext, null); } checkPolarisServiceBootstrappedForRealm(realmContext); @@ -254,18 +260,15 @@ public synchronized EntityCache getOrCreateEntityCache( } /** - * In this method we check if Service was bootstrapped for a given realm, i.e. - * that all the - * entities were created (root principal, root principal role, etc) If service - * was not - * bootstrapped we are throwing IllegalStateException exception That will cause - * service to crash - * and force user to run Bootstrap command and initialize MetaStore and create - * all the required + * In this method we check if Service was bootstrapped for a given realm, i.e. that all the + * entities were created (root principal, root principal role, etc) If service was not + * bootstrapped we are throwing IllegalStateException exception That will cause service to crash + * and force user to run Bootstrap command and initialize MetaStore and create all the required * entities */ private void checkPolarisServiceBootstrappedForRealm(RealmContext realmContext) { - PolarisMetaStoreManager metaStoreManager = metaStoreManagerMap.get(realmContext.getRealmIdentifier()); + PolarisMetaStoreManager metaStoreManager = + metaStoreManagerMap.get(realmContext.getRealmIdentifier()); BasePersistence metaStore = sessionSupplierMap.get(realmContext.getRealmIdentifier()).get(); PolarisCallContext polarisContext = new PolarisCallContext(realmContext, metaStore); diff --git a/runtime/common/build.gradle.kts b/runtime/common/build.gradle.kts index e930a3e13c..aa118c89d6 100644 --- a/runtime/common/build.gradle.kts +++ b/runtime/common/build.gradle.kts @@ -24,6 +24,7 @@ plugins { dependencies { compileOnly(libs.smallrye.config.core) + implementation(project(":polaris-core")) implementation(project(":polaris-relational-jdbc")) implementation(platform(libs.quarkus.amazon.services.bom)) implementation("io.quarkiverse.amazonservices:quarkus-amazon-rds") diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java index e025206eb0..999e03d828 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java @@ -21,30 +21,34 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import javax.sql.DataSource; +import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Default implementation of {@link DataSourceResolver} that routes all realms - * and store types to a single default {@link DataSource}. This serves as both - * the production default and the base for multi-datasource extensions. + * Default implementation of {@link DataSourceResolver} that routes all realms and store types to a + * single default {@link DataSource}. This serves as both the production default and the base for + * multi-datasource extensions. */ @ApplicationScoped public class DefaultDataSourceResolver implements DataSourceResolver { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); - private final DataSource defaultDataSource; + private final DataSource defaultDataSource; - @Inject - public DefaultDataSourceResolver(DataSource defaultDataSource) { - this.defaultDataSource = defaultDataSource; - } + @Inject + public DefaultDataSourceResolver(DataSource defaultDataSource) { + this.defaultDataSource = defaultDataSource; + } - @Override - public DataSource resolve(String realmId, StoreType storeType) { - LOGGER.debug("Using default DataSource for realm '{}' and store '{}'", realmId, storeType); - return defaultDataSource; - } + @Override + public DataSource resolve(RealmContext realmContext, StoreType storeType) { + LOGGER.debug( + "Using default DataSource for realm '{}' and store '{}'", + realmContext.getRealmIdentifier(), + storeType); + return defaultDataSource; + } } From 96e7ff086815251a7027c9449557f774bcd543fc Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 10 Mar 2026 00:47:08 +0530 Subject: [PATCH 04/17] Address final nit: use import for RealmContext --- .../relational/jdbc/DataSourceResolver.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 6513511db5..8b06c52cbf 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -19,10 +19,13 @@ package org.apache.polaris.persistence.relational.jdbc; import javax.sql.DataSource; +import org.apache.polaris.core.context.RealmContext; /** - * Service to resolve the correct {@link DataSource} for a given realm and store type. This enables - * isolating different workloads (e.g., entity metadata vs metrics vs events) into different + * Service to resolve the correct {@link DataSource} for a given realm and store + * type. This enables + * isolating different workloads (e.g., entity metadata vs metrics vs events) + * into different * physical databases or connection pools. */ public interface DataSourceResolver { @@ -37,10 +40,9 @@ enum StoreType { /** * Resolves the DataSource for a given realm and store type. * - * @param realmId the realm identifier - * @param storeType the type of store (e.g., main, metrics, events) + * @param realmContext the realm context + * @param storeType the type of store (e.g., main, metrics, events) * @return the resolved DataSource */ - DataSource resolve( - org.apache.polaris.core.context.RealmContext realmContext, StoreType storeType); + DataSource resolve(RealmContext realmContext, StoreType storeType); } From 9a07e6e1b800a3f4931633c3f92c0969362f06f3 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 10 Mar 2026 09:08:58 +0530 Subject: [PATCH 05/17] Final formatting and naming consistency cleanup --- .../persistence/relational/jdbc/DataSourceResolver.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 8b06c52cbf..152eae46dd 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -22,10 +22,8 @@ import org.apache.polaris.core.context.RealmContext; /** - * Service to resolve the correct {@link DataSource} for a given realm and store - * type. This enables - * isolating different workloads (e.g., entity metadata vs metrics vs events) - * into different + * Service to resolve the correct {@link DataSource} for a given realm and store type. This enables + * isolating different workloads (e.g., entity metadata vs metrics vs events) into different * physical databases or connection pools. */ public interface DataSourceResolver { @@ -41,7 +39,7 @@ enum StoreType { * Resolves the DataSource for a given realm and store type. * * @param realmContext the realm context - * @param storeType the type of store (e.g., main, metrics, events) + * @param storeType the type of store (e.g., main, metrics, events) * @return the resolved DataSource */ DataSource resolve(RealmContext realmContext, StoreType storeType); From 75e409664aa3d4ab312d93511d6e925eda55990c Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Wed, 11 Mar 2026 12:52:48 +0530 Subject: [PATCH 06/17] Fix InactiveBeanException in non-JDBC profiles via lazy DataSource resolution --- .../common/config/jdbc/DefaultDataSourceResolver.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java index 999e03d828..94f2d4b75f 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java @@ -19,6 +19,7 @@ package org.apache.polaris.quarkus.common.config.jdbc; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import javax.sql.DataSource; import org.apache.polaris.core.context.RealmContext; @@ -36,10 +37,10 @@ public class DefaultDataSourceResolver implements DataSourceResolver { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); - private final DataSource defaultDataSource; + private final Instance defaultDataSource; @Inject - public DefaultDataSourceResolver(DataSource defaultDataSource) { + public DefaultDataSourceResolver(Instance defaultDataSource) { this.defaultDataSource = defaultDataSource; } @@ -49,6 +50,6 @@ public DataSource resolve(RealmContext realmContext, StoreType storeType) { "Using default DataSource for realm '{}' and store '{}'", realmContext.getRealmIdentifier(), storeType); - return defaultDataSource; + return defaultDataSource.get(); } } From fccb2540c1a9f7dfd4126e41f5c161297dffe33b Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Thu, 12 Mar 2026 16:42:22 +0530 Subject: [PATCH 07/17] Address review comments: tighten scope, use plain injection and @DefaultBean --- .../relational/jdbc/DataSourceResolver.java | 12 +++++------- .../relational/jdbc/JdbcMetaStoreManagerFactory.java | 5 ++--- .../config/jdbc/DefaultDataSourceResolver.java | 6 ++++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 152eae46dd..bc522e69e7 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -22,24 +22,22 @@ import org.apache.polaris.core.context.RealmContext; /** - * Service to resolve the correct {@link DataSource} for a given realm and store type. This enables - * isolating different workloads (e.g., entity metadata vs metrics vs events) into different - * physical databases or connection pools. + * Service to resolve the correct {@link DataSource} for a given realm and store type. + * Note: Currently this is implemented as a foundation for metastore routing, not a + * full system-wide routing layer. */ public interface DataSourceResolver { /** The type of store representing the workload pattern. */ enum StoreType { - METASTORE, - METRICS, - EVENTS + METASTORE } /** * Resolves the DataSource for a given realm and store type. * * @param realmContext the realm context - * @param storeType the type of store (e.g., main, metrics, events) + * @param storeType the type of store * @return the resolved DataSource */ DataSource resolve(RealmContext realmContext, StoreType storeType); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 3311c6f124..47f29fe10b 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -23,7 +23,6 @@ import io.smallrye.common.annotation.Identifier; import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import java.sql.SQLException; import java.time.Clock; @@ -77,7 +76,7 @@ public class JdbcMetaStoreManagerFactory implements MetaStoreManagerFactory { @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; - @Inject Instance dataSourceResolver; + @Inject DataSourceResolver dataSourceResolver; @Inject RelationalJdbcConfiguration relationalJdbcConfiguration; @@ -130,7 +129,7 @@ public DatasourceOperations getDatasourceOperations( RealmContext realmContext, DataSourceResolver.StoreType storeType) { DatasourceOperations databaseOperations; try { - DataSource resolvedDs = dataSourceResolver.get().resolve(realmContext, storeType); + DataSource resolvedDs = dataSourceResolver.resolve(realmContext, storeType); databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java index 94f2d4b75f..3de215df1b 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java @@ -18,6 +18,7 @@ */ package org.apache.polaris.quarkus.common.config.jdbc; +import io.quarkus.arc.DefaultBean; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -29,10 +30,11 @@ /** * Default implementation of {@link DataSourceResolver} that routes all realms and store types to a - * single default {@link DataSource}. This serves as both the production default and the base for - * multi-datasource extensions. + * single default {@link DataSource}. This implementation acts as a fallback; downstream users can + * provide their own {@link DataSourceResolver} bean to implement custom routing logic. */ @ApplicationScoped +@DefaultBean public class DefaultDataSourceResolver implements DataSourceResolver { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); From 9c22635a8e973f8879a0ce2dad2398a5ee49dd7d Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Fri, 13 Mar 2026 17:16:56 +0530 Subject: [PATCH 08/17] Address review comments: tighten scope, use @Identifier and producer pattern - Removed StoreType enum from DataSourceResolver interface - Simplified resolve() to accept only RealmContext - Moved DefaultDataSourceResolver back to polaris-relational-jdbc - Replaced @DefaultBean with @Identifier("polaris") per project convention - Added @Any to Instance injection - Added DataSourceResolver producer in ServiceProducers - Added dataSourceResolver config property to PersistenceConfiguration - Updated all getDatasourceOperations() call sites --- .../relational/jdbc/DataSourceResolver.java | 15 ++++--------- .../jdbc/DefaultDataSourceResolver.java | 22 ++++++++----------- .../jdbc/JdbcMetaStoreManagerFactory.java | 14 +++++------- runtime/service/build.gradle.kts | 2 +- .../service/config/ServiceProducers.java | 7 ++++++ .../persistence/PersistenceConfiguration.java | 7 ++++++ 6 files changed, 33 insertions(+), 34 deletions(-) rename {runtime/common/src/main/java/org/apache/polaris/quarkus/common/config => persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational}/jdbc/DefaultDataSourceResolver.java (68%) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index bc522e69e7..0c93f5e992 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -22,23 +22,16 @@ import org.apache.polaris.core.context.RealmContext; /** - * Service to resolve the correct {@link DataSource} for a given realm and store type. - * Note: Currently this is implemented as a foundation for metastore routing, not a - * full system-wide routing layer. + * Service to resolve the correct {@link DataSource} for a given realm. Note: Currently this is + * implemented as a foundation for metastore routing. */ public interface DataSourceResolver { - /** The type of store representing the workload pattern. */ - enum StoreType { - METASTORE - } - /** - * Resolves the DataSource for a given realm and store type. + * Resolves the DataSource for a given realm. * * @param realmContext the realm context - * @param storeType the type of store * @return the resolved DataSource */ - DataSource resolve(RealmContext realmContext, StoreType storeType); + DataSource resolve(RealmContext realmContext); } diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java similarity index 68% rename from runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java rename to persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java index 3de215df1b..3ffef4d5de 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/DefaultDataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java @@ -16,25 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.quarkus.common.config.jdbc; +package org.apache.polaris.persistence.relational.jdbc; -import io.quarkus.arc.DefaultBean; +import io.smallrye.common.annotation.Identifier; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import javax.sql.DataSource; import org.apache.polaris.core.context.RealmContext; -import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Default implementation of {@link DataSourceResolver} that routes all realms and store types to a - * single default {@link DataSource}. This implementation acts as a fallback; downstream users can - * provide their own {@link DataSourceResolver} bean to implement custom routing logic. + * Default implementation of {@link DataSourceResolver} that routes all realms to a single default + * {@link DataSource}. */ @ApplicationScoped -@DefaultBean +@Identifier("polaris") public class DefaultDataSourceResolver implements DataSourceResolver { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); @@ -42,16 +41,13 @@ public class DefaultDataSourceResolver implements DataSourceResolver { private final Instance defaultDataSource; @Inject - public DefaultDataSourceResolver(Instance defaultDataSource) { + public DefaultDataSourceResolver(@Any Instance defaultDataSource) { this.defaultDataSource = defaultDataSource; } @Override - public DataSource resolve(RealmContext realmContext, StoreType storeType) { - LOGGER.debug( - "Using default DataSource for realm '{}' and store '{}'", - realmContext.getRealmIdentifier(), - storeType); + public DataSource resolve(RealmContext realmContext) { + LOGGER.debug("Using default DataSource for realm '{}'", realmContext.getRealmIdentifier()); return defaultDataSource.get(); } } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 47f29fe10b..138f38d187 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -125,11 +125,10 @@ private void initializeForRealm( metaStoreManagerMap.put(realmId, metaStoreManager); } - public DatasourceOperations getDatasourceOperations( - RealmContext realmContext, DataSourceResolver.StoreType storeType) { + public DatasourceOperations getDatasourceOperations(RealmContext realmContext) { DatasourceOperations databaseOperations; try { - DataSource resolvedDs = dataSourceResolver.resolve(realmContext, storeType); + DataSource resolvedDs = dataSourceResolver.resolve(realmContext); databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); @@ -160,8 +159,7 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = - getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); + DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); @@ -226,8 +224,7 @@ public Map purgeRealms(Iterable realms) { public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( RealmContext realmContext) { if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = - getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); + DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); initializeForRealm(datasourceOperations, realmContext, null); checkPolarisServiceBootstrappedForRealm(realmContext); } @@ -237,8 +234,7 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( @Override public synchronized BasePersistence getOrCreateSession(RealmContext realmContext) { if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = - getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); + DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); initializeForRealm(datasourceOperations, realmContext, null); } checkPolarisServiceBootstrappedForRealm(realmContext); diff --git a/runtime/service/build.gradle.kts b/runtime/service/build.gradle.kts index 630b5cbe35..3df3a65219 100644 --- a/runtime/service/build.gradle.kts +++ b/runtime/service/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { implementation(project(":polaris-api-iceberg-service")) implementation(project(":polaris-api-catalog-service")) - runtimeOnly(project(":polaris-relational-jdbc")) + implementation(project(":polaris-relational-jdbc")) implementation(project(":polaris-runtime-defaults")) implementation(project(":polaris-runtime-common")) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 688243171b..98fa94b9c0 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -60,6 +60,7 @@ import org.apache.polaris.core.storage.StorageCredentialsVendor; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.core.storage.cache.StorageCredentialCacheConfig; +import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; import org.apache.polaris.service.auth.AuthenticationConfiguration; import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; @@ -228,6 +229,12 @@ public MetaStoreManagerFactory metaStoreManagerFactory( return metaStoreManagerFactories.select(Identifier.Literal.of(config.type())).get(); } + @Produces + public DataSourceResolver dataSourceResolver( + PersistenceConfiguration config, @Any Instance dataSourceResolvers) { + return dataSourceResolvers.select(Identifier.Literal.of(config.dataSourceResolver())).get(); + } + @Produces @RequestScoped public PolarisMetaStoreManager polarisMetaStoreManager( diff --git a/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java index 54e2b61284..0dd7a774ee 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java @@ -37,4 +37,11 @@ public interface PersistenceConfiguration { default boolean isAutoBootstrap() { return autoBootstrapTypes().contains(type()); } + + /** + * The type of the {@link org.apache.polaris.persistence.relational.jdbc.DataSourceResolver} to + * use. Only applicable when using JDBC persistence. + */ + @WithDefault("polaris") + String dataSourceResolver(); } From 1e00328bd9bb386c8f238dc6c3f62570d7355e0e Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Fri, 13 Mar 2026 20:58:17 +0530 Subject: [PATCH 09/17] Address final nits: Rename identifier to default and move config to JDBC configuration --- .../relational/jdbc/DefaultDataSourceResolver.java | 2 +- .../relational/jdbc/RelationalJdbcConfiguration.java | 5 +++++ .../apache/polaris/service/config/ServiceProducers.java | 7 +++++-- .../service/persistence/PersistenceConfiguration.java | 7 ------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java index 3ffef4d5de..111abba0fd 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java @@ -33,7 +33,7 @@ * {@link DataSource}. */ @ApplicationScoped -@Identifier("polaris") +@Identifier("default") public class DefaultDataSourceResolver implements DataSourceResolver { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java index adfcae5ab1..ac02ceb464 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java @@ -35,4 +35,9 @@ public interface RelationalJdbcConfiguration { * the JDBC connection metadata. Supported values: "postgresql", "cockroachdb", "h2" */ Optional databaseType(); + + /** The type of the {@link DataSourceResolver} to use. Defaults to "default". */ + default Optional dataSourceResolverType() { + return Optional.of("default"); + } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 98fa94b9c0..22d96ddb5e 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -61,6 +61,7 @@ import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.core.storage.cache.StorageCredentialCacheConfig; import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; +import org.apache.polaris.persistence.relational.jdbc.RelationalJdbcConfiguration; import org.apache.polaris.service.auth.AuthenticationConfiguration; import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; @@ -231,8 +232,10 @@ public MetaStoreManagerFactory metaStoreManagerFactory( @Produces public DataSourceResolver dataSourceResolver( - PersistenceConfiguration config, @Any Instance dataSourceResolvers) { - return dataSourceResolvers.select(Identifier.Literal.of(config.dataSourceResolver())).get(); + RelationalJdbcConfiguration jdbcConfig, + @Any Instance dataSourceResolvers) { + String type = jdbcConfig.dataSourceResolverType().orElse("default"); + return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); } @Produces diff --git a/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java index 0dd7a774ee..54e2b61284 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/persistence/PersistenceConfiguration.java @@ -37,11 +37,4 @@ public interface PersistenceConfiguration { default boolean isAutoBootstrap() { return autoBootstrapTypes().contains(type()); } - - /** - * The type of the {@link org.apache.polaris.persistence.relational.jdbc.DataSourceResolver} to - * use. Only applicable when using JDBC persistence. - */ - @WithDefault("polaris") - String dataSourceResolver(); } From 493c3861f2a9b25f2154a9a031cfd50e0630a5ef Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Mon, 16 Mar 2026 19:59:01 +0530 Subject: [PATCH 10/17] Refine DataSourceResolver configuration wiring per review --- .../relational/jdbc/RelationalJdbcConfiguration.java | 6 ++---- .../jdbc/QuarkusRelationalJdbcConfiguration.java | 10 +++++++++- .../polaris/service/config/ServiceProducers.java | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java index ac02ceb464..c6c144add5 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java @@ -36,8 +36,6 @@ public interface RelationalJdbcConfiguration { */ Optional databaseType(); - /** The type of the {@link DataSourceResolver} to use. Defaults to "default". */ - default Optional dataSourceResolverType() { - return Optional.of("default"); - } + /** The type of the {@link DataSourceResolver} to use. */ + String dataSourceResolverType(); } diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java index 7eba6eaad7..e2f7bfb088 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java @@ -19,7 +19,15 @@ package org.apache.polaris.quarkus.common.config.jdbc; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; import org.apache.polaris.persistence.relational.jdbc.RelationalJdbcConfiguration; @ConfigMapping(prefix = "polaris.persistence.relational.jdbc") -public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration {} +public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration { + + @Override + @WithName("datasource-resolver.type") + @WithDefault("default") + String dataSourceResolverType(); +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 22d96ddb5e..5802248515 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -234,7 +234,7 @@ public MetaStoreManagerFactory metaStoreManagerFactory( public DataSourceResolver dataSourceResolver( RelationalJdbcConfiguration jdbcConfig, @Any Instance dataSourceResolvers) { - String type = jdbcConfig.dataSourceResolverType().orElse("default"); + String type = jdbcConfig.dataSourceResolverType(); return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); } From 6311fbef4cc382e9f0e51877bea504b5a6fbd3b8 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Wed, 18 Mar 2026 11:35:21 +0530 Subject: [PATCH 11/17] Address final CDI producer wiring nits --- .../relational/jdbc/JdbcCdiProducers.java | 48 +++++++++++++++++++ .../jdbc/RelationalJdbcConfiguration.java | 5 +- ...anagerWithJdbcBasePersistenceImplTest.java | 5 ++ .../jdbc/MetricsReportPersistenceTest.java | 5 ++ ...ationalJdbcIdempotencyStorePostgresIT.java | 5 ++ runtime/service/build.gradle.kts | 2 +- .../service/config/ServiceProducers.java | 10 ---- 7 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java new file mode 100644 index 0000000000..d72a427e2d --- /dev/null +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java @@ -0,0 +1,48 @@ +/* + * 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.persistence.relational.jdbc; + +import io.smallrye.common.annotation.Identifier; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; + +/** + * CDI producers for JDBC-specific beans. Co-located with the JDBC persistence module so that + * downstream runtimes only need to include this module to get the default wiring. + */ +public class JdbcCdiProducers { + + /** + * Produces the active {@link DataSourceResolver} by selecting the bean identified by {@link + * RelationalJdbcConfiguration#dataSourceResolverType()}. + * + *

The result is {@link ApplicationScoped} because the datasource-resolver type cannot change + * at runtime. + */ + @Produces + @ApplicationScoped + public DataSourceResolver dataSourceResolver( + RelationalJdbcConfiguration jdbcConfig, + @Any Instance dataSourceResolvers) { + String type = jdbcConfig.dataSourceResolverType(); + return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); + } +} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java index c6c144add5..f5a0be7010 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java @@ -36,6 +36,9 @@ public interface RelationalJdbcConfiguration { */ Optional databaseType(); - /** The type of the {@link DataSourceResolver} to use. */ + /** + * The identifier of the {@link DataSourceResolver} bean to use. Used by {@link JdbcCdiProducers} + * to select the resolver via CDI's {@link io.smallrye.common.annotation.Identifier}. + */ String dataSourceResolverType(); } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java index ec1bc59691..9ecca8b66b 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java @@ -100,5 +100,10 @@ public Optional initialDelayInMs() { public Optional databaseType() { return Optional.of("h2"); } + + @Override + public String dataSourceResolverType() { + return "default"; + } } } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java index f2ae29972f..e779c04ecb 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java @@ -202,5 +202,10 @@ public Optional initialDelayInMs() { public Optional databaseType() { return Optional.empty(); } + + @Override + public String dataSourceResolverType() { + return "default"; + } } } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java index ee95671dcd..9063e418dd 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java @@ -82,6 +82,11 @@ public Optional initialDelayInMs() { public Optional databaseType() { return Optional.empty(); } + + @Override + public String dataSourceResolverType() { + return "default"; + } }; DatasourceOperations ops = new DatasourceOperations(dataSource, cfg); try (InputStream is = diff --git a/runtime/service/build.gradle.kts b/runtime/service/build.gradle.kts index 3df3a65219..630b5cbe35 100644 --- a/runtime/service/build.gradle.kts +++ b/runtime/service/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { implementation(project(":polaris-api-iceberg-service")) implementation(project(":polaris-api-catalog-service")) - implementation(project(":polaris-relational-jdbc")) + runtimeOnly(project(":polaris-relational-jdbc")) implementation(project(":polaris-runtime-defaults")) implementation(project(":polaris-runtime-common")) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index 5802248515..688243171b 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -60,8 +60,6 @@ import org.apache.polaris.core.storage.StorageCredentialsVendor; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.core.storage.cache.StorageCredentialCacheConfig; -import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; -import org.apache.polaris.persistence.relational.jdbc.RelationalJdbcConfiguration; import org.apache.polaris.service.auth.AuthenticationConfiguration; import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; @@ -230,14 +228,6 @@ public MetaStoreManagerFactory metaStoreManagerFactory( return metaStoreManagerFactories.select(Identifier.Literal.of(config.type())).get(); } - @Produces - public DataSourceResolver dataSourceResolver( - RelationalJdbcConfiguration jdbcConfig, - @Any Instance dataSourceResolvers) { - String type = jdbcConfig.dataSourceResolverType(); - return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); - } - @Produces @RequestScoped public PolarisMetaStoreManager polarisMetaStoreManager( From a36e745a518ce6c805130d41329716dab20b5dd4 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 24 Mar 2026 00:09:50 +0530 Subject: [PATCH 12/17] Reinstate StoreType, update config docs, and finalize CDI wiring --- .../nosql/api/PersistenceParams.java | 17 +++++++++++ ...CommitTraversalLimitExceededException.java | 29 +++++++++++++++++++ .../relational/jdbc/DataSourceResolver.java | 12 ++++++-- .../jdbc/DefaultDataSourceResolver.java | 7 +++-- .../jdbc/JdbcMetaStoreManagerFactory.java | 3 +- .../smallrye-polaris_persistence.md | 1 + ...rye-polaris_persistence_relational_jdbc.md | 1 + 7 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java diff --git a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java index 0fa9036ae2..0fdbcc9d6e 100644 --- a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java +++ b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java @@ -83,6 +83,17 @@ default RetryConfig retryConfig() { @WithDefault(DEFAULT_MAX_SERIALIZED_VALUE_SIZE_STRING) MemorySize maxSerializedValueSize(); + String DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING = "1000"; + int DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT = + Integer.parseInt(DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING); + + /** + * The maximum number of commits to traverse when searching for an offset in the commit log. This + * is a safeguard against unbounded resource consumption for large histories or invalid offsets. + */ + @WithDefault(DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING) + int maxCommitHistoryTraversalCount(); + @PolarisImmutable interface BuildablePersistenceParams extends PersistenceParams { static ImmutableBuildablePersistenceParams.Builder builder() { @@ -130,5 +141,11 @@ default int bucketizedBulkFetchSize() { default MemorySize maxSerializedValueSize() { return DEFAULT_MAX_SERIALIZED_VALUE_SIZE; } + + @Override + @Value.Default + default int maxCommitHistoryTraversalCount() { + return DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT; + } } } diff --git a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java new file mode 100644 index 0000000000..c90de7e495 --- /dev/null +++ b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java @@ -0,0 +1,29 @@ +/* + * 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.persistence.nosql.api.exceptions; + +/** + * Thrown when the commit history traversal exceeds the configured limit. This is a safeguard + * against unbounded resource consumption. + */ +public class CommitTraversalLimitExceededException extends PersistenceException { + public CommitTraversalLimitExceededException(String message) { + super(message); + } +} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java index 0c93f5e992..a64d6649ad 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DataSourceResolver.java @@ -27,11 +27,19 @@ */ public interface DataSourceResolver { + /** The type of store representing the workload pattern. */ + enum StoreType { + METASTORE, + METRICS, + EVENTS + } + /** - * Resolves the DataSource for a given realm. + * Resolves the DataSource for a given realm and store type. * * @param realmContext the realm context + * @param storeType the type of store * @return the resolved DataSource */ - DataSource resolve(RealmContext realmContext); + DataSource resolve(RealmContext realmContext, StoreType storeType); } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java index 111abba0fd..bc970d95d5 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java @@ -46,8 +46,11 @@ public DefaultDataSourceResolver(@Any Instance defaultDataSource) { } @Override - public DataSource resolve(RealmContext realmContext) { - LOGGER.debug("Using default DataSource for realm '{}'", realmContext.getRealmIdentifier()); + public DataSource resolve(RealmContext realmContext, StoreType storeType) { + LOGGER.debug( + "Using default DataSource for realm '{}' and store '{}'", + realmContext.getRealmIdentifier(), + storeType); return defaultDataSource.get(); } } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 138f38d187..94a99888ea 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -128,7 +128,8 @@ private void initializeForRealm( public DatasourceOperations getDatasourceOperations(RealmContext realmContext) { DatasourceOperations databaseOperations; try { - DataSource resolvedDs = dataSourceResolver.resolve(realmContext); + DataSource resolvedDs = + dataSourceResolver.resolve(realmContext, DataSourceResolver.StoreType.METASTORE); databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); diff --git a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md index c4887ec4a9..5f54e51005 100644 --- a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md +++ b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md @@ -33,3 +33,4 @@ build: | `polaris.persistence.max-index-stripe-size` | `128k` | `MemorySize` | | | `polaris.persistence.bucketized-bulk-fetch-size` | `16` | `int` | The number of objects to fetch at once via (`Persistence#bucketizedBulkFetches(Stream,
Class)`). | | `polaris.persistence.max-serialized-value-size` | `350k` | `MemorySize` | The maximum size of a serialized value in a persisted database row. | +| `polaris.persistence.max-commit-history-traversal-count` | `1000` | `int` | The maximum number of commits to traverse when searching for an offset in the commit log. This is a safeguard against unbounded resource consumption for large histories or invalid offsets. | diff --git a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md index e50f686542..90b77f36e1 100644 --- a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md +++ b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md @@ -25,6 +25,7 @@ build: | Property | Default Value | Type | Description | |----------|---------------|------|-------------| +| `polaris.persistence.relational.jdbc.datasource-resolver.type` | `default` | `string` | | | `polaris.persistence.relational.jdbc.max-retries` | | `int` | | | `polaris.persistence.relational.jdbc.max-duration-in-ms` | | `long` | | | `polaris.persistence.relational.jdbc.initial-delay-in-ms` | | `long` | | From 118e046bf16da6471e7cec431066fc68fc1ed937 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 24 Mar 2026 10:55:11 +0530 Subject: [PATCH 13/17] Address review feedback: Resolve separate DataSources for METASTORE, METRICS, and EVENTS in JdbcMetaStoreManagerFactory and use them in JdbcBasePersistenceImpl --- .../jdbc/JdbcBasePersistenceImpl.java | 132 +++++++++--------- .../jdbc/JdbcMetaStoreManagerFactory.java | 28 ++-- ...anagerWithJdbcBasePersistenceImplTest.java | 5 +- .../jdbc/MetricsReportPersistenceTest.java | 2 + 4 files changed, 94 insertions(+), 73 deletions(-) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java index b154a610d5..6ed3cddfdb 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java @@ -87,7 +87,9 @@ public class JdbcBasePersistenceImpl implements BasePersistence, IntegrationPers private static final Logger LOGGER = LoggerFactory.getLogger(JdbcBasePersistenceImpl.class); private final PolarisDiagnostics diagnostics; - private final DatasourceOperations datasourceOperations; + private final DatasourceOperations metastoreOps; + private final DatasourceOperations metricsOps; + private final DatasourceOperations eventOps; private final PrincipalSecretsGenerator secretsGenerator; private final PolarisStorageIntegrationProvider storageIntegrationProvider; private final String realmId; @@ -98,13 +100,17 @@ public class JdbcBasePersistenceImpl implements BasePersistence, IntegrationPers public JdbcBasePersistenceImpl( PolarisDiagnostics diagnostics, - DatasourceOperations databaseOperations, + DatasourceOperations metastoreOps, + DatasourceOperations metricsOps, + DatasourceOperations eventOps, PrincipalSecretsGenerator secretsGenerator, PolarisStorageIntegrationProvider storageIntegrationProvider, String realmId, int schemaVersion) { this.diagnostics = diagnostics; - this.datasourceOperations = databaseOperations; + this.metastoreOps = metastoreOps; + this.metricsOps = metricsOps; + this.eventOps = eventOps; this.secretsGenerator = secretsGenerator; this.storageIntegrationProvider = storageIntegrationProvider; this.realmId = realmId; @@ -129,7 +135,7 @@ public void writeEntity( originalEntity, null, (connection, preparedQuery) -> { - return datasourceOperations.executeUpdate(preparedQuery); + return metastoreOps.executeUpdate(preparedQuery); }); } catch (SQLException e) { throw new RuntimeException("Error persisting entity", e); @@ -142,7 +148,7 @@ public void writeEntities( @Nonnull List entities, List originalEntities) { try { - datasourceOperations.runWithinTransaction( + metastoreOps.runWithinTransaction( connection -> { for (int i = 0; i < entities.size(); i++) { PolarisBaseEntity entity = entities.get(i); @@ -160,7 +166,7 @@ public void writeEntities( continue; } persistEntity( - callCtx, entity, originalEntity, connection, datasourceOperations::execute); + callCtx, entity, originalEntity, connection, metastoreOps::execute); } return true; }); @@ -183,7 +189,7 @@ private void persistEntity( if (originalEntity == null) { try { List values = - modelEntity.toMap(datasourceOperations.getDatabaseType()).values().stream().toList(); + modelEntity.toMap(metastoreOps.getDatabaseType()).values().stream().toList(); queryAction.apply( connection, QueryGenerator.generateInsertQuery( @@ -192,7 +198,7 @@ private void persistEntity( values, realmId)); } catch (SQLException e) { - if (datasourceOperations.isConstraintViolation(e)) { + if (metastoreOps.isConstraintViolation(e)) { PolarisBaseEntity existingEntity = lookupEntityByName( callCtx, @@ -225,7 +231,7 @@ private void persistEntity( realmId); try { List values = - modelEntity.toMap(datasourceOperations.getDatabaseType()).values().stream().toList(); + modelEntity.toMap(metastoreOps.getDatabaseType()).values().stream().toList(); int rowsUpdated = queryAction.apply( connection, @@ -252,8 +258,8 @@ public void writeToGrantRecords( ModelGrantRecord modelGrantRecord = ModelGrantRecord.fromGrantRecord(grantRec); try { List values = - modelGrantRecord.toMap(datasourceOperations.getDatabaseType()).values().stream().toList(); - datasourceOperations.executeUpdate( + modelGrantRecord.toMap(metastoreOps.getDatabaseType()).values().stream().toList(); + metastoreOps.executeUpdate( QueryGenerator.generateInsertQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, values, realmId)); } catch (SQLException e) { @@ -275,7 +281,7 @@ public void writeEvents(@Nonnull List events) { ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, ModelEvent.fromEvent(events.getFirst()) - .toMap(datasourceOperations.getDatabaseType()) + .toMap(eventOps.getDatabaseType()) .values() .stream() .toList(), @@ -293,7 +299,7 @@ public void writeEvents(@Nonnull List events) { ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, ModelEvent.fromEvent(event) - .toMap(datasourceOperations.getDatabaseType()) + .toMap(eventOps.getDatabaseType()) .values() .stream() .toList(), @@ -307,7 +313,7 @@ public void writeEvents(@Nonnull List events) { } int totalUpdated = - datasourceOperations.executeBatchUpdate( + eventOps.executeBatchUpdate( new QueryGenerator.PreparedBatchQuery(expectedSql, parametersList)); if (totalUpdated == 0) { @@ -331,7 +337,7 @@ public void deleteEntity(@Nonnull PolarisCallContext callCtx, @Nonnull PolarisBa "realm_id", realmId); try { - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( ModelEntity.getAllColumnNames(schemaVersion), ModelEntity.TABLE_NAME, params)); } catch (SQLException e) { @@ -346,9 +352,9 @@ public void deleteFromGrantRecords( ModelGrantRecord modelGrantRecord = ModelGrantRecord.fromGrantRecord(grantRec); try { Map whereClause = - modelGrantRecord.toMap(datasourceOperations.getDatabaseType()); + modelGrantRecord.toMap(metastoreOps.getDatabaseType()); whereClause.put("realm_id", realmId); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, whereClause)); } catch (SQLException e) { @@ -364,7 +370,7 @@ public void deleteAllEntityGrantRecords( @Nonnull List grantsOnGrantee, @Nonnull List grantsOnSecurable) { try { - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQueryForEntityGrantRecords(entity, realmId)); } catch (SQLException e) { throw new RuntimeException( @@ -376,23 +382,23 @@ public void deleteAllEntityGrantRecords( public void deleteAll(@Nonnull PolarisCallContext callCtx) { try { Map params = Map.of("realm_id", realmId); - datasourceOperations.runWithinTransaction( + metastoreOps.runWithinTransaction( connection -> { - datasourceOperations.execute( + metastoreOps.execute( connection, QueryGenerator.generateDeleteQuery( ModelEntity.getAllColumnNames(schemaVersion), ModelEntity.TABLE_NAME, params)); - datasourceOperations.execute( + metastoreOps.execute( connection, QueryGenerator.generateDeleteQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, params)); - datasourceOperations.execute( + metastoreOps.execute( connection, QueryGenerator.generateDeleteQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, params)); - datasourceOperations.execute( + metastoreOps.execute( connection, QueryGenerator.generateDeleteQuery( ModelPolicyMappingRecord.ALL_COLUMNS, @@ -443,7 +449,7 @@ public PolarisBaseEntity lookupEntityByName( @Nullable private PolarisBaseEntity getPolarisBaseEntity(QueryGenerator.PreparedQuery query) { try { - var results = datasourceOperations.executeSelect(query, new ModelEntity(schemaVersion)); + var results = metastoreOps.executeSelect(query, new ModelEntity(schemaVersion)); if (results.isEmpty()) { return null; } else if (results.size() > 1) { @@ -469,7 +475,7 @@ public List lookupEntities( QueryGenerator.generateSelectQueryWithEntityIds(realmId, schemaVersion, entityIds); try { Map idMap = - datasourceOperations.executeSelect(query, new ModelEntity(schemaVersion)).stream() + metastoreOps.executeSelect(query, new ModelEntity(schemaVersion)).stream() .collect( Collectors.toMap( e -> new PolarisEntityId(e.getCatalogId(), e.getId()), Function.identity())); @@ -565,7 +571,7 @@ public Page listEntities( pageToken, ModelEntity.ENTITY_LOOKUP_COLUMNS); AtomicReference> results = new AtomicReference<>(); - datasourceOperations.executeSelectOverStream( + metastoreOps.executeSelectOverStream( query, new EntityNameLookupRecordConverter(), stream -> { @@ -600,7 +606,7 @@ public Page listFullEntities( pageToken, ModelEntity.getAllColumnNames(schemaVersion)); AtomicReference> results = new AtomicReference<>(); - datasourceOperations.executeSelectOverStream( + metastoreOps.executeSelectOverStream( query, new ModelEntity(schemaVersion), stream -> { @@ -651,7 +657,7 @@ public PolarisGrantRecord lookupGrantRecord( realmId); try { var results = - datasourceOperations.executeSelect( + metastoreOps.executeSelect( QueryGenerator.generateSelectQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, params), new ModelGrantRecord()); @@ -683,7 +689,7 @@ public List loadAllGrantRecordsOnSecurable( realmId); try { var results = - datasourceOperations.executeSelect( + metastoreOps.executeSelect( QueryGenerator.generateSelectQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, params), new ModelGrantRecord()); @@ -706,7 +712,7 @@ public List loadAllGrantRecordsOnGrantee( "grantee_catalog_id", granteeCatalogId, "grantee_id", granteeId, "realm_id", realmId); try { var results = - datasourceOperations.executeSelect( + metastoreOps.executeSelect( QueryGenerator.generateSelectQuery( ModelGrantRecord.ALL_COLUMNS, ModelGrantRecord.TABLE_NAME, params), new ModelGrantRecord()); @@ -735,7 +741,7 @@ public boolean hasChildren( } try { var results = - datasourceOperations.executeSelect( + metastoreOps.executeSelect( QueryGenerator.generateSelectQuery( ModelEntity.getAllColumnNames(schemaVersion), ModelEntity.TABLE_NAME, params), new ModelEntity(schemaVersion)); @@ -749,17 +755,17 @@ public boolean hasChildren( } static int loadSchemaVersion( - DatasourceOperations datasourceOperations, boolean fallbackOnDoesNotExist) { + DatasourceOperations metastoreOps, boolean fallbackOnDoesNotExist) { PreparedQuery query = QueryGenerator.generateVersionQuery(); try { List schemaVersion = - datasourceOperations.executeSelect(query, new SchemaVersion()); + metastoreOps.executeSelect(query, new SchemaVersion()); if (schemaVersion == null || schemaVersion.size() != 1) { throw new RuntimeException("Failed to retrieve schema version"); } return schemaVersion.getFirst().getValue(); } catch (SQLException e) { - if (fallbackOnDoesNotExist && datasourceOperations.isRelationDoesNotExist(e)) { + if (fallbackOnDoesNotExist && metastoreOps.isRelationDoesNotExist(e)) { return SchemaVersion.MINIMUM.getValue(); } LOGGER.error("Failed to load schema version due to {}", e.getMessage(), e); @@ -767,14 +773,14 @@ static int loadSchemaVersion( } } - static boolean entityTableExists(DatasourceOperations datasourceOperations) { + static boolean entityTableExists(DatasourceOperations metastoreOps) { PreparedQuery query = QueryGenerator.generateEntityTableExistQuery(); try { List entities = - datasourceOperations.executeSelect(query, new ModelEntity()); + metastoreOps.executeSelect(query, new ModelEntity()); return entities != null && !entities.isEmpty(); } catch (SQLException e) { - if (datasourceOperations.isRelationDoesNotExist(e)) { + if (metastoreOps.isRelationDoesNotExist(e)) { return false; } throw new IllegalStateException("Failed to check if Entities table exists", e); @@ -798,7 +804,7 @@ Optional> hasOverlappingSiblings( QueryGenerator.generateOverlapQuery( realmId, schemaVersion, entity.getCatalogId(), entity.getBaseLocation()); try { - var results = datasourceOperations.executeSelect(query, new ModelEntity(schemaVersion)); + var results = metastoreOps.executeSelect(query, new ModelEntity(schemaVersion)); if (!results.isEmpty()) { StorageLocation entityLocation = StorageLocation.of(entity.getBaseLocation()); for (PolarisBaseEntity result : results) { @@ -831,7 +837,7 @@ public PolarisPrincipalSecrets loadPrincipalSecrets( Map params = Map.of("principal_client_id", clientId, "realm_id", realmId); try { var results = - datasourceOperations.executeSelect( + metastoreOps.executeSelect( QueryGenerator.generateSelectQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, @@ -872,9 +878,9 @@ public PolarisPrincipalSecrets generateNewPrincipalSecrets( // write new principal secrets try { List values = - lookupPrincipalSecrets.toMap(datasourceOperations.getDatabaseType()).values().stream() + lookupPrincipalSecrets.toMap(metastoreOps.getDatabaseType()).values().stream() .toList(); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateInsertQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, @@ -907,12 +913,12 @@ public PolarisPrincipalSecrets storePrincipalSecrets( try { ModelPrincipalAuthenticationData modelPrincipalAuthenticationData = ModelPrincipalAuthenticationData.fromPrincipalAuthenticationData(principalSecrets); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateInsertQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, modelPrincipalAuthenticationData - .toMap(datasourceOperations.getDatabaseType()) + .toMap(metastoreOps.getDatabaseType()) .values() .stream() .toList(), @@ -968,12 +974,12 @@ public PolarisPrincipalSecrets rotatePrincipalSecrets( try { ModelPrincipalAuthenticationData modelPrincipalAuthenticationData = ModelPrincipalAuthenticationData.fromPrincipalAuthenticationData(principalSecrets); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateUpdateQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, modelPrincipalAuthenticationData - .toMap(datasourceOperations.getDatabaseType()) + .toMap(metastoreOps.getDatabaseType()) .values() .stream() .toList(), @@ -998,7 +1004,7 @@ public void deletePrincipalSecrets( Map params = Map.of("principal_client_id", clientId, "principal_id", principalId, "realm_id", realmId); try { - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, ModelPrincipalAuthenticationData.TABLE_NAME, @@ -1018,7 +1024,7 @@ public void deletePrincipalSecrets( public void writeToPolicyMappingRecords( @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord record) { try { - datasourceOperations.runWithinTransaction( + metastoreOps.runWithinTransaction( connection -> { PolicyType policyType = PolicyType.fromCode(record.getPolicyTypeCode()); Preconditions.checkArgument( @@ -1027,7 +1033,7 @@ public void writeToPolicyMappingRecords( ModelPolicyMappingRecord.fromPolicyMappingRecord(record); List values = modelPolicyMappingRecord - .toMap(datasourceOperations.getDatabaseType()) + .toMap(metastoreOps.getDatabaseType()) .values() .stream() .toList(); @@ -1040,7 +1046,7 @@ public void writeToPolicyMappingRecords( if (policyType.isInheritable()) { return handleInheritablePolicy(callCtx, record, insertPolicyMappingQuery, connection); } else { - datasourceOperations.execute(connection, insertPolicyMappingQuery); + metastoreOps.execute(connection, insertPolicyMappingQuery); } return true; }); @@ -1091,15 +1097,15 @@ private boolean handleInheritablePolicy( ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, modelPolicyMappingRecord - .toMap(datasourceOperations.getDatabaseType()) + .toMap(metastoreOps.getDatabaseType()) .values() .stream() .toList(), updateClause); - datasourceOperations.execute(connection, updateQuery); + metastoreOps.execute(connection, updateQuery); } else { // record doesn't exist do an insert. - datasourceOperations.executeUpdate(insertQuery); + metastoreOps.executeUpdate(insertQuery); } return true; } @@ -1110,9 +1116,9 @@ public void deleteFromPolicyMappingRecords( var modelPolicyMappingRecord = ModelPolicyMappingRecord.fromPolicyMappingRecord(record); try { Map objectMap = - modelPolicyMappingRecord.toMap(datasourceOperations.getDatabaseType()); + modelPolicyMappingRecord.toMap(metastoreOps.getDatabaseType()); objectMap.put("realm_id", realmId); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, @@ -1141,7 +1147,7 @@ public void deleteAllEntityPolicyMappingRecords( queryParams.put("target_id", entity.getId()); } queryParams.put("realm_id", realmId); - datasourceOperations.executeUpdate( + metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, @@ -1241,7 +1247,7 @@ public List loadAllTargetsOnPolicy( private List fetchPolicyMappingRecords( QueryGenerator.PreparedQuery query) { try { - var results = datasourceOperations.executeSelect(query, new ModelPolicyMappingRecord()); + var results = metastoreOps.executeSelect(query, new ModelPolicyMappingRecord()); return results == null ? Collections.emptyList() : results; } catch (SQLException e) { throw new RuntimeException( @@ -1288,7 +1294,7 @@ private interface QueryAction { /** Returns the datasource operations to use for metrics persistence. */ private DatasourceOperations getMetricsDatasource() { - return datasourceOperations; + return metricsOps; } @Override @@ -1306,15 +1312,15 @@ public void writeCommitReport(@Nonnull CommitMetricsRecord record) { // ========== Internal Metrics JDBC methods ========== private void writeScanMetricsReport(@Nonnull ModelScanMetricsReport report) { - DatasourceOperations metricsOps = getMetricsDatasource(); + DatasourceOperations metricsOpsToUse = getMetricsDatasource(); try { PreparedQuery pq = QueryGenerator.generateInsertQuery( ModelScanMetricsReport.ALL_COLUMNS, ModelScanMetricsReport.TABLE_NAME, - report.toMap(metricsOps.getDatabaseType()).values().stream().toList(), + report.toMap(metricsOpsToUse.getDatabaseType()).values().stream().toList(), realmId); - metricsOps.executeUpdate(pq); + metricsOpsToUse.executeUpdate(pq); } catch (SQLException e) { throw new RuntimeException( String.format("Failed to write scan metrics report due to %s", e.getMessage()), e); @@ -1322,15 +1328,15 @@ private void writeScanMetricsReport(@Nonnull ModelScanMetricsReport report) { } private void writeCommitMetricsReport(@Nonnull ModelCommitMetricsReport report) { - DatasourceOperations metricsOps = getMetricsDatasource(); + DatasourceOperations metricsOpsToUse = getMetricsDatasource(); try { PreparedQuery pq = QueryGenerator.generateInsertQuery( ModelCommitMetricsReport.ALL_COLUMNS, ModelCommitMetricsReport.TABLE_NAME, - report.toMap(metricsOps.getDatabaseType()).values().stream().toList(), + report.toMap(metricsOpsToUse.getDatabaseType()).values().stream().toList(), realmId); - metricsOps.executeUpdate(pq); + metricsOpsToUse.executeUpdate(pq); } catch (SQLException e) { throw new RuntimeException( String.format("Failed to write commit metrics report due to %s", e.getMessage()), e); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 94a99888ea..d60d9b9ee5 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -98,7 +98,7 @@ protected PolarisMetaStoreManager createNewMetaStoreManager() { } private void initializeForRealm( - DatasourceOperations datasourceOperations, + DatasourceOperations metastoreOps, RealmContext realmContext, RootCredentialsSet rootCredentialsSet) { // Materialize realmId so that background tasks that don't have an active @@ -107,15 +107,22 @@ private void initializeForRealm( // determine schemaVersion once per realm final int schemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion( - datasourceOperations, + metastoreOps, realmConfig.getConfig(BehaviorChangeConfiguration.SCHEMA_VERSION_FALL_BACK_ON_DNE)); + DatasourceOperations metricsOps = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METRICS); + DatasourceOperations eventOps = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.EVENTS); + sessionSupplierMap.put( realmId, () -> new JdbcBasePersistenceImpl( diagnostics, - datasourceOperations, + metastoreOps, + metricsOps, + eventOps, secretsGenerator(realmId, rootCredentialsSet), storageIntegrationProvider, realmId, @@ -125,11 +132,11 @@ private void initializeForRealm( metaStoreManagerMap.put(realmId, metaStoreManager); } - public DatasourceOperations getDatasourceOperations(RealmContext realmContext) { + public DatasourceOperations getDatasourceOperations( + RealmContext realmContext, DataSourceResolver.StoreType storeType) { DatasourceOperations databaseOperations; try { - DataSource resolvedDs = - dataSourceResolver.resolve(realmContext, DataSourceResolver.StoreType.METASTORE); + DataSource resolvedDs = dataSourceResolver.resolve(realmContext, storeType); databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); @@ -160,7 +167,8 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); @@ -225,7 +233,8 @@ public Map purgeRealms(Iterable realms) { public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( RealmContext realmContext) { if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); initializeForRealm(datasourceOperations, realmContext, null); checkPolarisServiceBootstrappedForRealm(realmContext); } @@ -235,7 +244,8 @@ public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager( @Override public synchronized BasePersistence getOrCreateSession(RealmContext realmContext) { if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) { - DatasourceOperations datasourceOperations = getDatasourceOperations(realmContext); + DatasourceOperations datasourceOperations = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); initializeForRealm(datasourceOperations, realmContext, null); } checkPolarisServiceBootstrappedForRealm(realmContext); diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java index 9ecca8b66b..15174a1ac6 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java @@ -32,6 +32,7 @@ import org.apache.polaris.core.persistence.BasePolarisMetaStoreManagerTest; import org.apache.polaris.core.persistence.PolarisTestMetaStoreManager; import org.h2.jdbcx.JdbcConnectionPool; +import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; import org.mockito.Mockito; public abstract class AtomicMetastoreManagerWithJdbcBasePersistenceImplTest @@ -69,8 +70,10 @@ protected PolarisTestMetaStoreManager createPolarisTestMetaStoreManager() { new JdbcBasePersistenceImpl( diagServices, datasourceOperations, + datasourceOperations, + datasourceOperations, RANDOM_SECRETS, - Mockito.mock(), + Mockito.mock(PolarisStorageIntegrationProvider.class), realmContext.getRealmIdentifier(), schemaVersion()); AtomicOperationMetaStoreManager metaStoreManager = diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java index e779c04ecb..2b052b4d4d 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java @@ -74,6 +74,8 @@ PolarisStorageIntegration getStorageIntegrationForConfig( new JdbcBasePersistenceImpl( diagnostics, datasourceOperations, + datasourceOperations, + datasourceOperations, PrincipalSecretsGenerator.RANDOM_SECRETS, storageProvider, "TEST_REALM", From dc8ffda1d1d12b46e3577a14a3ea445571d6f34b Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Wed, 25 Mar 2026 12:44:19 +0530 Subject: [PATCH 14/17] Address further review feedback: Revert unrelated changes, move CDI producers, and fix schema bootstrap for isolated DataSources --- .../nosql/api/PersistenceParams.java | 17 ------- ...CommitTraversalLimitExceededException.java | 29 ----------- .../relational/jdbc/JdbcCdiProducers.java | 48 ------------------- .../jdbc/JdbcMetaStoreManagerFactory.java | 30 +++++++----- .../jdbc/RelationalJdbcConfiguration.java | 5 -- ...anagerWithJdbcBasePersistenceImplTest.java | 5 -- .../jdbc/MetricsReportPersistenceTest.java | 5 -- ...ationalJdbcIdempotencyStorePostgresIT.java | 7 +-- .../QuarkusRelationalJdbcConfiguration.java | 2 - .../persistence/jdbc/JdbcCdiProducers.java | 32 +++++++++++++ 10 files changed, 51 insertions(+), 129 deletions(-) delete mode 100644 persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java delete mode 100644 persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java create mode 100644 runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java diff --git a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java index 0fdbcc9d6e..0fa9036ae2 100644 --- a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java +++ b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/PersistenceParams.java @@ -83,17 +83,6 @@ default RetryConfig retryConfig() { @WithDefault(DEFAULT_MAX_SERIALIZED_VALUE_SIZE_STRING) MemorySize maxSerializedValueSize(); - String DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING = "1000"; - int DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT = - Integer.parseInt(DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING); - - /** - * The maximum number of commits to traverse when searching for an offset in the commit log. This - * is a safeguard against unbounded resource consumption for large histories or invalid offsets. - */ - @WithDefault(DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT_STRING) - int maxCommitHistoryTraversalCount(); - @PolarisImmutable interface BuildablePersistenceParams extends PersistenceParams { static ImmutableBuildablePersistenceParams.Builder builder() { @@ -141,11 +130,5 @@ default int bucketizedBulkFetchSize() { default MemorySize maxSerializedValueSize() { return DEFAULT_MAX_SERIALIZED_VALUE_SIZE; } - - @Override - @Value.Default - default int maxCommitHistoryTraversalCount() { - return DEFAULT_MAX_COMMIT_HISTORY_TRAVERSAL_COUNT; - } } } diff --git a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java b/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java deleted file mode 100644 index c90de7e495..0000000000 --- a/persistence/nosql/persistence/api/src/main/java/org/apache/polaris/persistence/nosql/api/exceptions/CommitTraversalLimitExceededException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.persistence.nosql.api.exceptions; - -/** - * Thrown when the commit history traversal exceeds the configured limit. This is a safeguard - * against unbounded resource consumption. - */ -public class CommitTraversalLimitExceededException extends PersistenceException { - public CommitTraversalLimitExceededException(String message) { - super(message); - } -} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java deleted file mode 100644 index d72a427e2d..0000000000 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcCdiProducers.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.persistence.relational.jdbc; - -import io.smallrye.common.annotation.Identifier; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Any; -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.Produces; - -/** - * CDI producers for JDBC-specific beans. Co-located with the JDBC persistence module so that - * downstream runtimes only need to include this module to get the default wiring. - */ -public class JdbcCdiProducers { - - /** - * Produces the active {@link DataSourceResolver} by selecting the bean identified by {@link - * RelationalJdbcConfiguration#dataSourceResolverType()}. - * - *

The result is {@link ApplicationScoped} because the datasource-resolver type cannot change - * at runtime. - */ - @Produces - @ApplicationScoped - public DataSourceResolver dataSourceResolver( - RelationalJdbcConfiguration jdbcConfig, - @Any Instance dataSourceResolvers) { - String type = jdbcConfig.dataSourceResolverType(); - return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); - } -} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index d60d9b9ee5..62e2864494 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -167,33 +167,39 @@ public synchronized Map bootstrapRealms( for (String realm : bootstrapOptions.realms()) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realm)) { - DatasourceOperations datasourceOperations = + DatasourceOperations metastoreOps = getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METASTORE); - int currentSchemaVersion = - JdbcBasePersistenceImpl.loadSchemaVersion(datasourceOperations, true); + DatasourceOperations metricsOps = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.METRICS); + DatasourceOperations eventOps = + getDatasourceOperations(realmContext, DataSourceResolver.StoreType.EVENTS); + + int currentSchemaVersion = JdbcBasePersistenceImpl.loadSchemaVersion(metastoreOps, true); int requestedSchemaVersion = JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions); int effectiveSchemaVersion = JdbcBootstrapUtils.getRealmBootstrapSchemaVersion( - datasourceOperations.getDatabaseType(), + metastoreOps.getDatabaseType(), currentSchemaVersion, requestedSchemaVersion, - JdbcBasePersistenceImpl.entityTableExists(datasourceOperations)); + JdbcBasePersistenceImpl.entityTableExists(metastoreOps)); LOGGER.info( "Effective schema version: {} for bootstrapping realm: {}", effectiveSchemaVersion, realm); + try { - // Run the set-up script to create the tables. - datasourceOperations.executeScript( - datasourceOperations - .getDatabaseType() - .openInitScriptResource(effectiveSchemaVersion)); + // Run the set-up script to create the tables on all data sources. + metastoreOps.executeScript( + metastoreOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); + metricsOps.executeScript( + metricsOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); + eventOps.executeScript( + eventOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); } catch (SQLException e) { throw new RuntimeException( String.format("Error executing sql script: %s", e.getMessage()), e); } - initializeForRealm( - datasourceOperations, realmContext, bootstrapOptions.rootCredentialsSet()); + initializeForRealm(metastoreOps, realmContext, bootstrapOptions.rootCredentialsSet()); PolarisMetaStoreManager metaStoreManager = metaStoreManagerMap.get(realmContext.getRealmIdentifier()); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java index f5a0be7010..2482437c5e 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java @@ -36,9 +36,4 @@ public interface RelationalJdbcConfiguration { */ Optional databaseType(); - /** - * The identifier of the {@link DataSourceResolver} bean to use. Used by {@link JdbcCdiProducers} - * to select the resolver via CDI's {@link io.smallrye.common.annotation.Identifier}. - */ - String dataSourceResolverType(); } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java index 15174a1ac6..2fe3ed1e48 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java @@ -103,10 +103,5 @@ public Optional initialDelayInMs() { public Optional databaseType() { return Optional.of("h2"); } - - @Override - public String dataSourceResolverType() { - return "default"; - } } } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java index 2b052b4d4d..d6eafc39b0 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/MetricsReportPersistenceTest.java @@ -204,10 +204,5 @@ public Optional initialDelayInMs() { public Optional databaseType() { return Optional.empty(); } - - @Override - public String dataSourceResolverType() { - return "default"; - } } } diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java index 9063e418dd..61613b52ba 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/idempotency/RelationalJdbcIdempotencyStorePostgresIT.java @@ -80,12 +80,7 @@ public Optional initialDelayInMs() { @Override public Optional databaseType() { - return Optional.empty(); - } - - @Override - public String dataSourceResolverType() { - return "default"; + return Optional.of("postgresql"); } }; DatasourceOperations ops = new DatasourceOperations(dataSource, cfg); diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java index e2f7bfb088..fbd7cff76b 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/config/jdbc/QuarkusRelationalJdbcConfiguration.java @@ -25,8 +25,6 @@ @ConfigMapping(prefix = "polaris.persistence.relational.jdbc") public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration { - - @Override @WithName("datasource-resolver.type") @WithDefault("default") String dataSourceResolverType(); diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java new file mode 100644 index 0000000000..a588470746 --- /dev/null +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java @@ -0,0 +1,32 @@ +package org.apache.polaris.quarkus.common.persistence.jdbc; + +import io.smallrye.common.annotation.Identifier; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import org.apache.polaris.persistence.relational.jdbc.DataSourceResolver; +import org.apache.polaris.quarkus.common.config.jdbc.QuarkusRelationalJdbcConfiguration; + +/** + * CDI producers for JDBC-specific beans. Moved to runtime-common to keep the persistence layer + * implementation-agnostic regarding configuration sources. + */ +public class JdbcCdiProducers { + + /** + * Produces the active {@link DataSourceResolver} by selecting the bean identified by {@link + * QuarkusRelationalJdbcConfiguration#dataSourceResolverType()}. + * + *

The result is {@link ApplicationScoped} because the datasource-resolver type cannot change + * at runtime. + */ + @Produces + @ApplicationScoped + public DataSourceResolver dataSourceResolver( + QuarkusRelationalJdbcConfiguration jdbcConfig, + @Any Instance dataSourceResolvers) { + String type = jdbcConfig.dataSourceResolverType(); + return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); + } +} From 7eb2304891be9b3c3387d946857c37c6d9f5a472 Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Tue, 31 Mar 2026 10:56:32 +0530 Subject: [PATCH 15/17] Finalize PR #3960: split v4 DDL, fix license headers, and move CDI producers to runtime-common --- .../relational/jdbc/DatabaseType.java | 23 +++- .../jdbc/JdbcMetaStoreManagerFactory.java | 15 ++- .../cockroachdb/schema-v4-events.sql | 45 +++++++ .../cockroachdb/schema-v4-metastore.sql | 112 +++++++++++++++++ .../cockroachdb/schema-v4-metrics.sql | 109 ++++++++++++++++ .../main/resources/h2/schema-v4-events.sql | 44 +++++++ .../main/resources/h2/schema-v4-metastore.sql | 109 ++++++++++++++++ .../main/resources/h2/schema-v4-metrics.sql | 108 ++++++++++++++++ .../resources/postgres/schema-v4-events.sql | 45 +++++++ .../postgres/schema-v4-metastore.sql | 118 ++++++++++++++++++ .../resources/postgres/schema-v4-metrics.sql | 109 ++++++++++++++++ .../persistence/jdbc/JdbcCdiProducers.java | 18 +++ .../smallrye-polaris_persistence.md | 1 - 13 files changed, 851 insertions(+), 5 deletions(-) create mode 100644 persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql create mode 100644 persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql create mode 100644 persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql create mode 100644 persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql create mode 100644 persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql create mode 100644 persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql create mode 100644 persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql create mode 100644 persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql create mode 100644 persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatabaseType.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatabaseType.java index cc104e1f39..affb3de8ae 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatabaseType.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatabaseType.java @@ -149,6 +149,16 @@ public static DatabaseType inferFromConnection( * caller. */ public InputStream openInitScriptResource(int schemaVersion) { + return openInitScriptResource(schemaVersion, null); + } + + /** + * Open an InputStream that contains data from an init script for a specific {@link + * DataSourceResolver.StoreType}. If a specialized script (e.g., schema-v4-metrics.sql) is not + * found, it falls back to the main script (schema-v4.sql). + */ + public InputStream openInitScriptResource( + int schemaVersion, DataSourceResolver.StoreType storeType) { // Validate schema version is within acceptable range for this database type int latestVersion = getLatestSchemaVersion(); if (schemaVersion <= 0 || schemaVersion > latestVersion) { @@ -158,10 +168,21 @@ public InputStream openInitScriptResource(int schemaVersion) { schemaVersion, this, latestVersion)); } + ClassLoader classLoader = DatasourceOperations.class.getClassLoader(); + if (storeType != null) { + String specializedResourceName = + String.format( + "%s/schema-v%d-%s.sql", + this.getDisplayName(), schemaVersion, storeType.name().toLowerCase(Locale.ROOT)); + InputStream specializedStream = classLoader.getResourceAsStream(specializedResourceName); + if (specializedStream != null) { + return specializedStream; + } + } + final String resourceName = String.format("%s/schema-v%d.sql", this.getDisplayName(), schemaVersion); - ClassLoader classLoader = DatasourceOperations.class.getClassLoader(); InputStream stream = classLoader.getResourceAsStream(resourceName); if (stream == null) { throw new IllegalStateException( diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 62e2864494..68ee080380 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -190,11 +190,20 @@ public synchronized Map bootstrapRealms( try { // Run the set-up script to create the tables on all data sources. metastoreOps.executeScript( - metastoreOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); + metastoreOps + .getDatabaseType() + .openInitScriptResource( + effectiveSchemaVersion, DataSourceResolver.StoreType.METASTORE)); metricsOps.executeScript( - metricsOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); + metricsOps + .getDatabaseType() + .openInitScriptResource( + effectiveSchemaVersion, DataSourceResolver.StoreType.METRICS)); eventOps.executeScript( - eventOps.getDatabaseType().openInitScriptResource(effectiveSchemaVersion)); + eventOps + .getDatabaseType() + .openInitScriptResource( + effectiveSchemaVersion, DataSourceResolver.StoreType.EVENTS)); } catch (SQLException e) { throw new RuntimeException( String.format("Error executing sql script: %s", e.getMessage()), e); diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql new file mode 100644 index 0000000000..b52d5ce3e9 --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql @@ -0,0 +1,45 @@ +-- +-- 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. + +-- Events schema for v4 (CockroachDB) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INT4 NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS events ( + realm_id TEXT NOT NULL, + catalog_id TEXT NOT NULL, + event_id TEXT NOT NULL, + request_id TEXT, + event_type TEXT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + resource_type TEXT NOT NULL, + resource_identifier TEXT NOT NULL, + additional_properties JSONB NOT NULL DEFAULT '{}'::JSONB, + PRIMARY KEY (event_id) +); diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql new file mode 100644 index 0000000000..f01bbf72c8 --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql @@ -0,0 +1,112 @@ +-- +-- 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. + +-- Metastore schema for v4 (CockroachDB) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INT4 NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS entities ( + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + id BIGINT NOT NULL, + parent_id BIGINT NOT NULL, + name TEXT NOT NULL, + entity_version INT4 NOT NULL, + type_code INT4 NOT NULL, + sub_type_code INT4 NOT NULL, + create_timestamp BIGINT NOT NULL, + drop_timestamp BIGINT NOT NULL, + purge_timestamp BIGINT NOT NULL, + to_purge_timestamp BIGINT NOT NULL, + last_update_timestamp BIGINT NOT NULL, + properties JSONB not null default '{}'::JSONB, + internal_properties JSONB not null default '{}'::JSONB, + grant_records_version INT4 NOT NULL, + location_without_scheme TEXT, + PRIMARY KEY (realm_id, id), + CONSTRAINT constraint_name UNIQUE (realm_id, catalog_id, parent_id, type_code, name) +); + +CREATE INDEX IF NOT EXISTS idx_entities ON entities (realm_id, catalog_id, id); +CREATE INDEX IF NOT EXISTS idx_locations + ON entities USING btree (realm_id, parent_id, location_without_scheme) + WHERE location_without_scheme IS NOT NULL; + +CREATE TABLE IF NOT EXISTS grant_records ( + realm_id TEXT NOT NULL, + securable_catalog_id BIGINT NOT NULL, + securable_id BIGINT NOT NULL, + grantee_catalog_id BIGINT NOT NULL, + grantee_id BIGINT NOT NULL, + privilege_code INT4, + PRIMARY KEY (realm_id, securable_catalog_id, securable_id, grantee_catalog_id, grantee_id, privilege_code) +); + +CREATE TABLE IF NOT EXISTS principal_authentication_data ( + realm_id TEXT NOT NULL, + principal_id BIGINT NOT NULL, + principal_client_id VARCHAR(255) NOT NULL, + main_secret_hash VARCHAR(255) NOT NULL, + secondary_secret_hash VARCHAR(255) NOT NULL, + secret_salt VARCHAR(255) NOT NULL, + PRIMARY KEY (realm_id, principal_client_id) +); + +CREATE TABLE IF NOT EXISTS policy_mapping_record ( + realm_id TEXT NOT NULL, + target_catalog_id BIGINT NOT NULL, + target_id BIGINT NOT NULL, + policy_type_code INT4 NOT NULL, + policy_catalog_id BIGINT NOT NULL, + policy_id BIGINT NOT NULL, + parameters JSONB NOT NULL DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, target_catalog_id, target_id, policy_type_code, policy_catalog_id, policy_id) +); + +CREATE INDEX IF NOT EXISTS idx_policy_mapping_record ON policy_mapping_record (realm_id, policy_type_code, policy_catalog_id, policy_id, target_catalog_id, target_id); + +CREATE TABLE IF NOT EXISTS idempotency_records ( + realm_id TEXT NOT NULL, + idempotency_key TEXT NOT NULL, + operation_type TEXT NOT NULL, + resource_id TEXT NOT NULL, + http_status INT4, + error_subtype TEXT, + response_summary TEXT, + response_headers TEXT, + finalized_at TIMESTAMP, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + heartbeat_at TIMESTAMP, + executor_id TEXT, + expires_at TIMESTAMP, + PRIMARY KEY (realm_id, idempotency_key) +); + +CREATE INDEX IF NOT EXISTS idx_idemp_realm_expires + ON idempotency_records (realm_id, expires_at); diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql new file mode 100644 index 0000000000..f1248039e6 --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql @@ -0,0 +1,109 @@ +-- +-- 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. + +-- Metrics schema for v4 (CockroachDB) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INT4 NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS scan_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT, + schema_id INT4, + filter_expression TEXT, + projected_field_ids TEXT, + projected_field_names TEXT, + result_data_files BIGINT DEFAULT 0, + result_delete_files BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_data_manifests BIGINT DEFAULT 0, + total_delete_manifests BIGINT DEFAULT 0, + scanned_data_manifests BIGINT DEFAULT 0, + scanned_delete_manifests BIGINT DEFAULT 0, + skipped_data_manifests BIGINT DEFAULT 0, + skipped_delete_manifests BIGINT DEFAULT 0, + skipped_data_files BIGINT DEFAULT 0, + skipped_delete_files BIGINT DEFAULT 0, + total_planning_duration_ms BIGINT DEFAULT 0, + equality_delete_files BIGINT DEFAULT 0, + positional_delete_files BIGINT DEFAULT 0, + indexed_delete_files BIGINT DEFAULT 0, + total_delete_file_size_bytes BIGINT DEFAULT 0, + metadata JSONB DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_scan_report_timestamp ON scan_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_scan_report_lookup ON scan_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); + +CREATE TABLE IF NOT EXISTS commit_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT NOT NULL, + sequence_number BIGINT, + operation TEXT NOT NULL, + added_data_files BIGINT DEFAULT 0, + removed_data_files BIGINT DEFAULT 0, + total_data_files BIGINT DEFAULT 0, + added_delete_files BIGINT DEFAULT 0, + removed_delete_files BIGINT DEFAULT 0, + total_delete_files BIGINT DEFAULT 0, + added_equality_delete_files BIGINT DEFAULT 0, + removed_equality_delete_files BIGINT DEFAULT 0, + added_positional_delete_files BIGINT DEFAULT 0, + removed_positional_delete_files BIGINT DEFAULT 0, + added_records BIGINT DEFAULT 0, + removed_records BIGINT DEFAULT 0, + total_records BIGINT DEFAULT 0, + added_file_size_bytes BIGINT DEFAULT 0, + removed_file_size_bytes BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_duration_ms BIGINT DEFAULT 0, + attempts INT4 DEFAULT 1, + metadata JSONB DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_commit_report_timestamp ON commit_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_commit_report_lookup ON commit_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql new file mode 100644 index 0000000000..cdb91286fb --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql @@ -0,0 +1,44 @@ +-- +-- 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. + +-- Events schema for v4 (H2) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET SCHEMA POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key VARCHAR PRIMARY KEY, + version_value INTEGER NOT NULL +); +MERGE INTO version (version_key, version_value) + KEY (version_key) + VALUES ('version', 4); +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS events ( + realm_id TEXT NOT NULL, + catalog_id TEXT NOT NULL, + event_id TEXT NOT NULL, + request_id TEXT, + event_type TEXT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + resource_type TEXT NOT NULL, + resource_identifier TEXT NOT NULL, + additional_properties TEXT NOT NULL, + PRIMARY KEY (event_id) +); diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql new file mode 100644 index 0000000000..7f90316a9c --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql @@ -0,0 +1,109 @@ +-- +-- 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. + +-- Metastore schema for v4 (H2) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET SCHEMA POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key VARCHAR PRIMARY KEY, + version_value INTEGER NOT NULL +); +MERGE INTO version (version_key, version_value) + KEY (version_key) + VALUES ('version', 4); +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS entities ( + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + id BIGINT NOT NULL, + parent_id BIGINT NOT NULL, + name TEXT NOT NULL, + entity_version INT NOT NULL, + type_code INT NOT NULL, + sub_type_code INT NOT NULL, + create_timestamp BIGINT NOT NULL, + drop_timestamp BIGINT NOT NULL, + purge_timestamp BIGINT NOT NULL, + to_purge_timestamp BIGINT NOT NULL, + last_update_timestamp BIGINT NOT NULL, + properties TEXT NOT NULL DEFAULT '{}', + internal_properties TEXT NOT NULL DEFAULT '{}', + grant_records_version INT NOT NULL, + location_without_scheme TEXT, + PRIMARY KEY (realm_id, id), + CONSTRAINT constraint_name UNIQUE (realm_id, catalog_id, parent_id, type_code, name) +); + +CREATE INDEX IF NOT EXISTS idx_entities ON entities (realm_id, catalog_id, id); +CREATE INDEX IF NOT EXISTS idx_locations ON entities(realm_id, catalog_id, location_without_scheme); + +CREATE TABLE IF NOT EXISTS grant_records ( + realm_id TEXT NOT NULL, + securable_catalog_id BIGINT NOT NULL, + securable_id BIGINT NOT NULL, + grantee_catalog_id BIGINT NOT NULL, + grantee_id BIGINT NOT NULL, + privilege_code INTEGER, + PRIMARY KEY (realm_id, securable_catalog_id, securable_id, grantee_catalog_id, grantee_id, privilege_code) +); + +CREATE TABLE IF NOT EXISTS principal_authentication_data ( + realm_id TEXT NOT NULL, + principal_id BIGINT NOT NULL, + principal_client_id VARCHAR(255) NOT NULL, + main_secret_hash VARCHAR(255) NOT NULL, + secondary_secret_hash VARCHAR(255) NOT NULL, + secret_salt VARCHAR(255) NOT NULL, + PRIMARY KEY (realm_id, principal_client_id) +); + +CREATE TABLE IF NOT EXISTS policy_mapping_record ( + realm_id TEXT NOT NULL, + target_catalog_id BIGINT NOT NULL, + target_id BIGINT NOT NULL, + policy_type_code INTEGER NOT NULL, + policy_catalog_id BIGINT NOT NULL, + policy_id BIGINT NOT NULL, + parameters TEXT NOT NULL DEFAULT '{}', + PRIMARY KEY (realm_id, target_catalog_id, target_id, policy_type_code, policy_catalog_id, policy_id) +); + +CREATE INDEX IF NOT EXISTS idx_policy_mapping_record ON policy_mapping_record (realm_id, policy_type_code, policy_catalog_id, policy_id, target_catalog_id, target_id); + +CREATE TABLE IF NOT EXISTS idempotency_records ( + realm_id TEXT NOT NULL, + idempotency_key TEXT NOT NULL, + operation_type TEXT NOT NULL, + resource_id TEXT NOT NULL, + http_status INTEGER, + error_subtype TEXT, + response_summary TEXT, + response_headers TEXT, + finalized_at TIMESTAMP, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + heartbeat_at TIMESTAMP, + executor_id TEXT, + expires_at TIMESTAMP, + PRIMARY KEY (realm_id, idempotency_key) +); + +CREATE INDEX IF NOT EXISTS idx_idemp_realm_expires + ON idempotency_records (realm_id, expires_at); diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql new file mode 100644 index 0000000000..895a452ddc --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql @@ -0,0 +1,108 @@ +-- +-- 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. + +-- Metrics schema for v4 (H2) +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET SCHEMA POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key VARCHAR PRIMARY KEY, + version_value INTEGER NOT NULL +); +MERGE INTO version (version_key, version_value) + KEY (version_key) + VALUES ('version', 4); +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS scan_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT, + schema_id INTEGER, + filter_expression TEXT, + projected_field_ids TEXT, + projected_field_names TEXT, + result_data_files BIGINT DEFAULT 0, + result_delete_files BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_data_manifests BIGINT DEFAULT 0, + total_delete_manifests BIGINT DEFAULT 0, + scanned_data_manifests BIGINT DEFAULT 0, + scanned_delete_manifests BIGINT DEFAULT 0, + skipped_data_manifests BIGINT DEFAULT 0, + skipped_delete_manifests BIGINT DEFAULT 0, + skipped_data_files BIGINT DEFAULT 0, + skipped_delete_files BIGINT DEFAULT 0, + total_planning_duration_ms BIGINT DEFAULT 0, + equality_delete_files BIGINT DEFAULT 0, + positional_delete_files BIGINT DEFAULT 0, + indexed_delete_files BIGINT DEFAULT 0, + total_delete_file_size_bytes BIGINT DEFAULT 0, + metadata TEXT NOT NULL DEFAULT '{}', + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_scan_report_timestamp ON scan_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_scan_report_lookup ON scan_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); + +CREATE TABLE IF NOT EXISTS commit_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT NOT NULL, + sequence_number BIGINT, + operation TEXT NOT NULL, + added_data_files BIGINT DEFAULT 0, + removed_data_files BIGINT DEFAULT 0, + total_data_files BIGINT DEFAULT 0, + added_delete_files BIGINT DEFAULT 0, + removed_delete_files BIGINT DEFAULT 0, + total_delete_files BIGINT DEFAULT 0, + added_equality_delete_files BIGINT DEFAULT 0, + removed_equality_delete_files BIGINT DEFAULT 0, + added_positional_delete_files BIGINT DEFAULT 0, + removed_positional_delete_files BIGINT DEFAULT 0, + added_records BIGINT DEFAULT 0, + removed_records BIGINT DEFAULT 0, + total_records BIGINT DEFAULT 0, + added_file_size_bytes BIGINT DEFAULT 0, + removed_file_size_bytes BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_duration_ms BIGINT DEFAULT 0, + attempts INTEGER DEFAULT 1, + metadata TEXT NOT NULL DEFAULT '{}', + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_commit_report_timestamp ON commit_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_commit_report_lookup ON commit_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql new file mode 100644 index 0000000000..4a00ef22b4 --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql @@ -0,0 +1,45 @@ +-- +-- 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. + +-- Events schema for v4 +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INTEGER NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS events ( + realm_id TEXT NOT NULL, + catalog_id TEXT NOT NULL, + event_id TEXT NOT NULL, + request_id TEXT, + event_type TEXT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + resource_type TEXT NOT NULL, + resource_identifier TEXT NOT NULL, + additional_properties JSONB NOT NULL DEFAULT '{}'::JSONB, + PRIMARY KEY (event_id) +); diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql new file mode 100644 index 0000000000..522d9d4602 --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql @@ -0,0 +1,118 @@ +-- +-- 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. + +-- Metastore schema for v4 +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INTEGER NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS entities ( + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + id BIGINT NOT NULL, + parent_id BIGINT NOT NULL, + name TEXT NOT NULL, + entity_version INT NOT NULL, + type_code INT NOT NULL, + sub_type_code INT NOT NULL, + create_timestamp BIGINT NOT NULL, + drop_timestamp BIGINT NOT NULL, + purge_timestamp BIGINT NOT NULL, + to_purge_timestamp BIGINT NOT NULL, + last_update_timestamp BIGINT NOT NULL, + properties JSONB not null default '{}'::JSONB, + internal_properties JSONB not null default '{}'::JSONB, + grant_records_version INT NOT NULL, + location_without_scheme TEXT, + PRIMARY KEY (realm_id, id), + CONSTRAINT constraint_name UNIQUE (realm_id, catalog_id, parent_id, type_code, name) +); + +CREATE INDEX IF NOT EXISTS idx_entities ON entities (realm_id, catalog_id, id); +CREATE INDEX IF NOT EXISTS idx_locations + ON entities USING btree (realm_id, parent_id, location_without_scheme) + WHERE location_without_scheme IS NOT NULL; + +COMMENT ON TABLE entities IS 'all the entities'; + +CREATE TABLE IF NOT EXISTS grant_records ( + realm_id TEXT NOT NULL, + securable_catalog_id BIGINT NOT NULL, + securable_id BIGINT NOT NULL, + grantee_catalog_id BIGINT NOT NULL, + grantee_id BIGINT NOT NULL, + privilege_code INTEGER, + PRIMARY KEY (realm_id, securable_catalog_id, securable_id, grantee_catalog_id, grantee_id, privilege_code) +); + +COMMENT ON TABLE grant_records IS 'grant records for entities'; + +CREATE TABLE IF NOT EXISTS principal_authentication_data ( + realm_id TEXT NOT NULL, + principal_id BIGINT NOT NULL, + principal_client_id VARCHAR(255) NOT NULL, + main_secret_hash VARCHAR(255) NOT NULL, + secondary_secret_hash VARCHAR(255) NOT NULL, + secret_salt VARCHAR(255) NOT NULL, + PRIMARY KEY (realm_id, principal_client_id) +); + +COMMENT ON TABLE principal_authentication_data IS 'authentication data for client'; + +CREATE TABLE IF NOT EXISTS policy_mapping_record ( + realm_id TEXT NOT NULL, + target_catalog_id BIGINT NOT NULL, + target_id BIGINT NOT NULL, + policy_type_code INTEGER NOT NULL, + policy_catalog_id BIGINT NOT NULL, + policy_id BIGINT NOT NULL, + parameters JSONB NOT NULL DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, target_catalog_id, target_id, policy_type_code, policy_catalog_id, policy_id) +); + +CREATE INDEX IF NOT EXISTS idx_policy_mapping_record ON policy_mapping_record (realm_id, policy_type_code, policy_catalog_id, policy_id, target_catalog_id, target_id); + +CREATE TABLE IF NOT EXISTS idempotency_records ( + realm_id TEXT NOT NULL, + idempotency_key TEXT NOT NULL, + operation_type TEXT NOT NULL, + resource_id TEXT NOT NULL, + http_status INTEGER, + error_subtype TEXT, + response_summary TEXT, + response_headers TEXT, + finalized_at TIMESTAMP, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + heartbeat_at TIMESTAMP, + executor_id TEXT, + expires_at TIMESTAMP, + PRIMARY KEY (realm_id, idempotency_key) +); + +CREATE INDEX IF NOT EXISTS idx_idemp_realm_expires + ON idempotency_records (realm_id, expires_at); diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql new file mode 100644 index 0000000000..d0eb27eb9d --- /dev/null +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql @@ -0,0 +1,109 @@ +-- +-- 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. + +-- Metrics schema for v4 +CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA; +SET search_path TO POLARIS_SCHEMA; + +CREATE TABLE IF NOT EXISTS version ( + version_key TEXT PRIMARY KEY, + version_value INTEGER NOT NULL +); +INSERT INTO version (version_key, version_value) +VALUES ('version', 4) +ON CONFLICT (version_key) DO UPDATE +SET version_value = EXCLUDED.version_value; +COMMENT ON TABLE version IS 'the version of the JDBC schema in use'; + +CREATE TABLE IF NOT EXISTS scan_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT, + schema_id INTEGER, + filter_expression TEXT, + projected_field_ids TEXT, + projected_field_names TEXT, + result_data_files BIGINT DEFAULT 0, + result_delete_files BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_data_manifests BIGINT DEFAULT 0, + total_delete_manifests BIGINT DEFAULT 0, + scanned_data_manifests BIGINT DEFAULT 0, + scanned_delete_manifests BIGINT DEFAULT 0, + skipped_data_manifests BIGINT DEFAULT 0, + skipped_delete_manifests BIGINT DEFAULT 0, + skipped_data_files BIGINT DEFAULT 0, + skipped_delete_files BIGINT DEFAULT 0, + total_planning_duration_ms BIGINT DEFAULT 0, + equality_delete_files BIGINT DEFAULT 0, + positional_delete_files BIGINT DEFAULT 0, + indexed_delete_files BIGINT DEFAULT 0, + total_delete_file_size_bytes BIGINT DEFAULT 0, + metadata JSONB DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_scan_report_timestamp ON scan_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_scan_report_lookup ON scan_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); + +CREATE TABLE IF NOT EXISTS commit_metrics_report ( + report_id TEXT NOT NULL, + realm_id TEXT NOT NULL, + catalog_id BIGINT NOT NULL, + table_id BIGINT NOT NULL, + timestamp_ms BIGINT NOT NULL, + principal_name TEXT, + request_id TEXT, + otel_trace_id TEXT, + otel_span_id TEXT, + report_trace_id TEXT, + snapshot_id BIGINT NOT NULL, + sequence_number BIGINT, + operation TEXT NOT NULL, + added_data_files BIGINT DEFAULT 0, + removed_data_files BIGINT DEFAULT 0, + total_data_files BIGINT DEFAULT 0, + added_delete_files BIGINT DEFAULT 0, + removed_delete_files BIGINT DEFAULT 0, + total_delete_files BIGINT DEFAULT 0, + added_equality_delete_files BIGINT DEFAULT 0, + removed_equality_delete_files BIGINT DEFAULT 0, + added_positional_delete_files BIGINT DEFAULT 0, + removed_positional_delete_files BIGINT DEFAULT 0, + added_records BIGINT DEFAULT 0, + removed_records BIGINT DEFAULT 0, + total_records BIGINT DEFAULT 0, + added_file_size_bytes BIGINT DEFAULT 0, + removed_file_size_bytes BIGINT DEFAULT 0, + total_file_size_bytes BIGINT DEFAULT 0, + total_duration_ms BIGINT DEFAULT 0, + attempts INTEGER DEFAULT 1, + metadata JSONB DEFAULT '{}'::JSONB, + PRIMARY KEY (realm_id, report_id) +); + +CREATE INDEX IF NOT EXISTS idx_commit_report_timestamp ON commit_metrics_report(realm_id, timestamp_ms); +CREATE INDEX IF NOT EXISTS idx_commit_report_lookup ON commit_metrics_report(realm_id, catalog_id, table_id, timestamp_ms); diff --git a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java index a588470746..5829c50e74 100644 --- a/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java +++ b/runtime/common/src/main/java/org/apache/polaris/quarkus/common/persistence/jdbc/JdbcCdiProducers.java @@ -1,3 +1,21 @@ +/* + * 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.quarkus.common.persistence.jdbc; import io.smallrye.common.annotation.Identifier; diff --git a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md index 5f54e51005..c4887ec4a9 100644 --- a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md +++ b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence.md @@ -33,4 +33,3 @@ build: | `polaris.persistence.max-index-stripe-size` | `128k` | `MemorySize` | | | `polaris.persistence.bucketized-bulk-fetch-size` | `16` | `int` | The number of objects to fetch at once via (`Persistence#bucketizedBulkFetches(Stream,
Class)`). | | `polaris.persistence.max-serialized-value-size` | `350k` | `MemorySize` | The maximum size of a serialized value in a persisted database row. | -| `polaris.persistence.max-commit-history-traversal-count` | `1000` | `int` | The maximum number of commits to traverse when searching for an offset in the commit log. This is a safeguard against unbounded resource consumption for large histories or invalid offsets. | From 7ceaa697a47c8a3f0aa94662bd248e7b87caa46d Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Wed, 1 Apr 2026 14:58:04 +0530 Subject: [PATCH 16/17] Introduce DataSourceResolver for multi-datasource support in JDBC persistence (#3960) Drafted design doc, fixed purge for isolated stores, and resolved build failures. --- build.gradle.kts | 4 + .../jdbc/JdbcBasePersistenceImpl.java | 59 +++++++----- .../jdbc/RelationalJdbcConfiguration.java | 1 - .../cockroachdb/schema-v4-events.sql | 6 +- .../cockroachdb/schema-v4-metastore.sql | 6 +- .../cockroachdb/schema-v4-metrics.sql | 6 +- .../main/resources/h2/schema-v4-events.sql | 6 +- .../main/resources/h2/schema-v4-metastore.sql | 6 +- .../main/resources/h2/schema-v4-metrics.sql | 6 +- .../resources/postgres/schema-v4-events.sql | 6 +- .../postgres/schema-v4-metastore.sql | 6 +- .../resources/postgres/schema-v4-metrics.sql | 6 +- ...anagerWithJdbcBasePersistenceImplTest.java | 2 +- .../metastores/jdbc-multi-datasource.md | 90 +++++++++++++++++++ 14 files changed, 157 insertions(+), 53 deletions(-) create mode 100644 site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md diff --git a/build.gradle.kts b/build.gradle.kts index 322cb783f8..12a0045240 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ if (System.getProperty("idea.sync.active").toBoolean()) { eclipse { project { name = ideName } } tasks.named("rat").configure { + mustRunAfter(":polaris-config-docs-site:copyConfigSectionsToSite") // Gradle excludes.add("**/build/**") excludes.add("gradle/wrapper/gradle-wrapper*") @@ -147,6 +148,9 @@ tasks.named("rat").configure { // Rat can't scan binary images excludes.add("**/*.png") + + // Auto-generated site content (copied from docs by copyConfigSectionsToSite) + excludes.add("site/content/**") } tasks.register("buildPythonClient") { diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java index 6ed3cddfdb..995ff537e1 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java @@ -165,8 +165,7 @@ public void writeEntities( // already been updated after the creation. continue; } - persistEntity( - callCtx, entity, originalEntity, connection, metastoreOps::execute); + persistEntity(callCtx, entity, originalEntity, connection, metastoreOps::execute); } return true; }); @@ -298,10 +297,7 @@ public void writeEvents(@Nonnull List events) { QueryGenerator.generateInsertQuery( ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, - ModelEvent.fromEvent(event) - .toMap(eventOps.getDatabaseType()) - .values() - .stream() + ModelEvent.fromEvent(event).toMap(eventOps.getDatabaseType()).values().stream() .toList(), realmId); @@ -351,8 +347,7 @@ public void deleteFromGrantRecords( @Nonnull PolarisCallContext callCtx, @Nonnull PolarisGrantRecord grantRec) { ModelGrantRecord modelGrantRecord = ModelGrantRecord.fromGrantRecord(grantRec); try { - Map whereClause = - modelGrantRecord.toMap(metastoreOps.getDatabaseType()); + Map whereClause = modelGrantRecord.toMap(metastoreOps.getDatabaseType()); whereClause.put("realm_id", realmId); metastoreOps.executeUpdate( QueryGenerator.generateDeleteQuery( @@ -406,6 +401,32 @@ public void deleteAll(@Nonnull PolarisCallContext callCtx) { params)); return true; }); + + // Clear metrics store if isolated + metricsOps.runWithinTransaction( + connection -> { + metricsOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelScanMetricsReport.ALL_COLUMNS, ModelScanMetricsReport.TABLE_NAME, params)); + metricsOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelCommitMetricsReport.ALL_COLUMNS, + ModelCommitMetricsReport.TABLE_NAME, + params)); + return true; + }); + + // Clear events store if isolated + eventOps.runWithinTransaction( + connection -> { + eventOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, params)); + return true; + }); } catch (SQLException e) { throw new RuntimeException( String.format("Failed to delete all due to %s", e.getMessage()), e); @@ -754,12 +775,10 @@ public boolean hasChildren( } } - static int loadSchemaVersion( - DatasourceOperations metastoreOps, boolean fallbackOnDoesNotExist) { + static int loadSchemaVersion(DatasourceOperations metastoreOps, boolean fallbackOnDoesNotExist) { PreparedQuery query = QueryGenerator.generateVersionQuery(); try { - List schemaVersion = - metastoreOps.executeSelect(query, new SchemaVersion()); + List schemaVersion = metastoreOps.executeSelect(query, new SchemaVersion()); if (schemaVersion == null || schemaVersion.size() != 1) { throw new RuntimeException("Failed to retrieve schema version"); } @@ -776,8 +795,7 @@ static int loadSchemaVersion( static boolean entityTableExists(DatasourceOperations metastoreOps) { PreparedQuery query = QueryGenerator.generateEntityTableExistQuery(); try { - List entities = - metastoreOps.executeSelect(query, new ModelEntity()); + List entities = metastoreOps.executeSelect(query, new ModelEntity()); return entities != null && !entities.isEmpty(); } catch (SQLException e) { if (metastoreOps.isRelationDoesNotExist(e)) { @@ -878,8 +896,7 @@ public PolarisPrincipalSecrets generateNewPrincipalSecrets( // write new principal secrets try { List values = - lookupPrincipalSecrets.toMap(metastoreOps.getDatabaseType()).values().stream() - .toList(); + lookupPrincipalSecrets.toMap(metastoreOps.getDatabaseType()).values().stream().toList(); metastoreOps.executeUpdate( QueryGenerator.generateInsertQuery( ModelPrincipalAuthenticationData.ALL_COLUMNS, @@ -1032,10 +1049,7 @@ public void writeToPolicyMappingRecords( ModelPolicyMappingRecord modelPolicyMappingRecord = ModelPolicyMappingRecord.fromPolicyMappingRecord(record); List values = - modelPolicyMappingRecord - .toMap(metastoreOps.getDatabaseType()) - .values() - .stream() + modelPolicyMappingRecord.toMap(metastoreOps.getDatabaseType()).values().stream() .toList(); PreparedQuery insertPolicyMappingQuery = QueryGenerator.generateInsertQuery( @@ -1096,10 +1110,7 @@ private boolean handleInheritablePolicy( QueryGenerator.generateUpdateQuery( ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, - modelPolicyMappingRecord - .toMap(metastoreOps.getDatabaseType()) - .values() - .stream() + modelPolicyMappingRecord.toMap(metastoreOps.getDatabaseType()).values().stream() .toList(), updateClause); metastoreOps.execute(connection, updateQuery); diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java index 2482437c5e..adfcae5ab1 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java @@ -35,5 +35,4 @@ public interface RelationalJdbcConfiguration { * the JDBC connection metadata. Supported values: "postgresql", "cockroachdb", "h2" */ Optional databaseType(); - } diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql index b52d5ce3e9..2a244098d1 100644 --- a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-events.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql index f01bbf72c8..cc33b0b3c3 100644 --- a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metastore.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql index f1248039e6..da7d7bee02 100644 --- a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql +++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4-metrics.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql index cdb91286fb..fe1c351adf 100644 --- a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-events.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql index 7f90316a9c..8a145fce0f 100644 --- a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metastore.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql index 895a452ddc..ab4fefe97e 100644 --- a/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql +++ b/persistence/relational-jdbc/src/main/resources/h2/schema-v4-metrics.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql index 4a00ef22b4..af80fcb80a 100644 --- a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-events.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql index 522d9d4602..e43f72a924 100644 --- a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metastore.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql index d0eb27eb9d..e3541787b5 100644 --- a/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql +++ b/persistence/relational-jdbc/src/main/resources/postgres/schema-v4-metrics.sql @@ -1,10 +1,10 @@ -- -- 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 +-- 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 +-- "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 diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java index 2fe3ed1e48..12127cb118 100644 --- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java +++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/AtomicMetastoreManagerWithJdbcBasePersistenceImplTest.java @@ -31,8 +31,8 @@ import org.apache.polaris.core.persistence.AtomicOperationMetaStoreManager; import org.apache.polaris.core.persistence.BasePolarisMetaStoreManagerTest; import org.apache.polaris.core.persistence.PolarisTestMetaStoreManager; -import org.h2.jdbcx.JdbcConnectionPool; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; +import org.h2.jdbcx.JdbcConnectionPool; import org.mockito.Mockito; public abstract class AtomicMetastoreManagerWithJdbcBasePersistenceImplTest diff --git a/site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md b/site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md new file mode 100644 index 0000000000..2ca16a372f --- /dev/null +++ b/site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md @@ -0,0 +1,90 @@ + +# Design: Multi-DataSource support in JDBC persistence + +## Goal +The goal of this design is to decouple `DataSource` selection from the core JDBC persistence logic. This allows for: +1. **Workload Isolation**: Separating the Metastore, Metrics reporting, and Event logging into different physical databases or connection pools. +2. **Scalable Multi-Tenancy**: Enabling per-realm (tenant) routing to support large-scale deployments. + +## Core Interface: `DataSourceResolver` +A service interface designed to resolve the correct `DataSource` based on the workload's metadata. + +```java +public interface DataSourceResolver { + enum StoreType { + METASTORE, + METRICS, + EVENTS + } + + DataSource resolve(RealmContext realmContext, StoreType storeType); +} +``` + +### Key Components +- **`DataSourceResolver`**: SPI for resolution logic. +- **`DefaultDataSourceResolver`**: Backward-compatible implementation that returns the single primary `DataSource` for all requests. +- **`JdbcMetaStoreManagerFactory`**: Overwrites the resolution logic to use the `DataSourceResolver`. +- **`JdbcBasePersistenceImpl`**: Manages three separate `DatasourceOperations` objects. + +## Schema Management and Evolution + +### Functional SQL Script Splitting +To support hosting different workloads on different databases, the monolithic `schema-vX.sql` scripts are split into functional components: +- `schema-vX-metastore.sql`: Definitions for `polaris_entities`, `polaris_grant_records`, and `polaris_schema_version`. +- `schema-vX-metrics.sql`: Definitions for `polaris_metrics_scan` and `polaris_metrics_commit`. +- `schema-vX-events.sql`: Definitions for `polaris_events`. + +### Version Authority +The **Metastore** remains the single source of truth for the realm's logical schema version. The `polaris_schema_version` table is exclusively maintained within the `METASTORE` data source. All associated stores (Metrics, Events) are expected to be physically compatible with this detected version. + +## Operational Behaviors + +### Bootstrap +When a realm is bootstrapped via `JdbcMetaStoreManagerFactory.bootstrapRealms()`: +1. **Resolution**: The `DataSourceResolver` is called for each `StoreType` (METASTORE, METRICS, EVENTS). +2. **Initialization**: Each resolved data source is initialized with its specific functional SQL script. +3. **Idempotency**: If all three `StoreType` mappings point to the same physical database, the scripts are applied sequentially. The DDL is designed to be idempotent to prevent errors during this "merged" initialization. + +### Purge (Cleanup) +When a realm is purged: +1. The `purge()` operation is initiated. +2. The `JdbcBasePersistenceImpl.deleteAll()` method is triggered. +3. **Multi-Store Cleanup**: `deleteAll()` executes `DELETE` commands targeting the specific `realm_id` across all three `DatasourceOperations`: + - `metastoreOps`: Clears entities, grants, secrets, and policy mappings. + - `metricsOps`: Clears `scan_metrics_report` and `commit_metrics_report`. + - `eventOps`: Clears the `events` table. +4. This ensures that all data across all three potential data sources is cleaned up for the specified `realm_id`. + +## Schema Upgrades +Upgrading a Polaris deployment from version N to N+1 involves: +1. **Detection**: The service detects that the `metastore`'s `polaris_schema_version` is below the requested level. +2. **Execution**: The migration logic resolves each `StoreType` and applies the relevant "vN-to-vN+1" upgrade script. +3. **Consistency**: Because the process is unified within the `JdbcMetaStoreManagerFactory`, it provides a single point of failure and ensures that all data sources are either upgraded or rolled back (where possible). + +## Scalable Multi-Tenancy +The SPI allows for sophisticated implementations that can: +- Route "Enterprise" realms to dedicated high-performance clusters. +- Move realms between physical databases by updating the resolver's internal mapping metadata (e.g., during a maintenance window). + +## Benefits +- **Zero Impact on Existing Users**: The default configuration maintains a single-datasource model. +- **Future-Proof**: Provides the necessary hooks for table-level connection pool separation and physical database sharding. +- **Maintainable**: The functional script split clearly defines the table categories and their associated workloads. From 9df42bea6e73ddf0e1c90b30c7f96b0bc494accc Mon Sep 17 00:00:00 2001 From: Subham Sangwan Date: Thu, 2 Apr 2026 07:18:43 +0530 Subject: [PATCH 17/17] Refine Multi-DataSource support: selective bootstrap, independent versioning, and heterogeneous storage support --- .../relational/jdbc/DatasourceOperations.java | 33 +++++++++- .../jdbc/JdbcBasePersistenceImpl.java | 62 ++++++++++++------- .../relational/jdbc/JdbcBootstrapUtils.java | 6 +- .../jdbc/JdbcMetaStoreManagerFactory.java | 7 ++- .../jdbc-multi-datasource.md | 0 5 files changed, 79 insertions(+), 29 deletions(-) rename site/content/in-dev/unreleased/{metastores => proposals}/jdbc-multi-datasource.md (100%) diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java index 4006ef28af..33bb8c6ab2 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java @@ -94,6 +94,33 @@ DatabaseType getDatabaseType() { return databaseType; } + /** + * Checks if a table exists in the database. + * + * @param tableName The name of the table to check for. + * @return true if the table exists, false otherwise. + * @throws SQLException if a database access error occurs. + */ + public boolean tableExists(String tableName) throws SQLException { + try (Connection connection = borrowConnection()) { + var metaData = connection.getMetaData(); + // Try uppercase first (Standard/H2) + try (ResultSet resultSet = + metaData.getTables( + null, null, tableName.toUpperCase(Locale.ROOT), new String[] {"TABLE"})) { + if (resultSet.next()) { + return true; + } + } + // Try lowercase (Postgres/CockroachDB) + try (ResultSet resultSet = + metaData.getTables( + null, null, tableName.toLowerCase(Locale.ROOT), new String[] {"TABLE"})) { + return resultSet.next(); + } + } + } + /** * Execute SQL script and close the associated input stream * @@ -326,7 +353,8 @@ private boolean isRetryable(SQLException e) { || e.getMessage().toLowerCase(Locale.ROOT).contains("connection reset"); } - // TODO: consider refactoring to use a retry library, inorder to have fair retries + // TODO: consider refactoring to use a retry library, inorder to have fair + // retries // and more knobs for tuning retry pattern. @VisibleForTesting T withRetries(Operation operation) throws SQLException { @@ -347,7 +375,8 @@ T withRetries(Operation operation) throws SQLException { } catch (SQLException | RuntimeException e) { SQLException sqlException; if (e instanceof RuntimeException) { - // Handle Exceptions from ResultSet Iterator consumer, as it throws a RTE, ignore RTE from + // Handle Exceptions from ResultSet Iterator consumer, as it throws a RTE, + // ignore RTE from // the transactions. if (e.getCause() instanceof SQLException && !(e instanceof EntityAlreadyExistsException)) { diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java index 995ff537e1..0d80041b34 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java @@ -269,6 +269,10 @@ public void writeToGrantRecords( @Override public void writeEvents(@Nonnull List events) { + if (eventOps == null) { + LOGGER.debug("Skipping events write as no JDBC events store is configured"); + return; + } if (events.isEmpty()) { return; // or throw if empty list is invalid } @@ -403,30 +407,36 @@ public void deleteAll(@Nonnull PolarisCallContext callCtx) { }); // Clear metrics store if isolated - metricsOps.runWithinTransaction( - connection -> { - metricsOps.execute( - connection, - QueryGenerator.generateDeleteQuery( - ModelScanMetricsReport.ALL_COLUMNS, ModelScanMetricsReport.TABLE_NAME, params)); - metricsOps.execute( - connection, - QueryGenerator.generateDeleteQuery( - ModelCommitMetricsReport.ALL_COLUMNS, - ModelCommitMetricsReport.TABLE_NAME, - params)); - return true; - }); + if (metricsOps.tableExists(ModelScanMetricsReport.TABLE_NAME)) { + metricsOps.runWithinTransaction( + connection -> { + metricsOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelScanMetricsReport.ALL_COLUMNS, + ModelScanMetricsReport.TABLE_NAME, + params)); + metricsOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelCommitMetricsReport.ALL_COLUMNS, + ModelCommitMetricsReport.TABLE_NAME, + params)); + return true; + }); + } // Clear events store if isolated - eventOps.runWithinTransaction( - connection -> { - eventOps.execute( - connection, - QueryGenerator.generateDeleteQuery( - ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, params)); - return true; - }); + if (eventOps.tableExists(ModelEvent.TABLE_NAME)) { + eventOps.runWithinTransaction( + connection -> { + eventOps.execute( + connection, + QueryGenerator.generateDeleteQuery( + ModelEvent.ALL_COLUMNS, ModelEvent.TABLE_NAME, params)); + return true; + }); + } } catch (SQLException e) { throw new RuntimeException( String.format("Failed to delete all due to %s", e.getMessage()), e); @@ -1324,6 +1334,10 @@ public void writeCommitReport(@Nonnull CommitMetricsRecord record) { private void writeScanMetricsReport(@Nonnull ModelScanMetricsReport report) { DatasourceOperations metricsOpsToUse = getMetricsDatasource(); + if (metricsOpsToUse == null) { + LOGGER.debug("Skipping scan metrics report write as no JDBC metrics store is configured"); + return; + } try { PreparedQuery pq = QueryGenerator.generateInsertQuery( @@ -1340,6 +1354,10 @@ private void writeScanMetricsReport(@Nonnull ModelScanMetricsReport report) { private void writeCommitMetricsReport(@Nonnull ModelCommitMetricsReport report) { DatasourceOperations metricsOpsToUse = getMetricsDatasource(); + if (metricsOpsToUse == null) { + LOGGER.debug("Skipping commit metrics report write as no JDBC metrics store is configured"); + return; + } try { PreparedQuery pq = QueryGenerator.generateInsertQuery( diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtils.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtils.java index 3e0391ca79..a1a663370c 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtils.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtils.java @@ -58,7 +58,8 @@ public static int getRealmBootstrapSchemaVersion( return 1; } } else { - // A truly fresh start. Default to latest version for this database type for auto-detection, + // A truly fresh start. Default to latest version for this database type for + // auto-detection, // otherwise use the specified version. return requiredSchemaVersion == -1 ? latestSchemaVersion : requiredSchemaVersion; } @@ -66,7 +67,8 @@ public static int getRealmBootstrapSchemaVersion( // Handle auto-detection on an existing installation (current version > 0). if (requiredSchemaVersion == -1) { - // Use the current version if realms already exist; otherwise, use latest version for the new + // Use the current version if realms already exist; otherwise, use latest + // version for the new // realm. return hasAlreadyBootstrappedRealms ? currentSchemaVersion : latestSchemaVersion; } diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java index 68ee080380..52467d9f84 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java @@ -134,14 +134,15 @@ private void initializeForRealm( public DatasourceOperations getDatasourceOperations( RealmContext realmContext, DataSourceResolver.StoreType storeType) { - DatasourceOperations databaseOperations; try { DataSource resolvedDs = dataSourceResolver.resolve(realmContext, storeType); - databaseOperations = new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); + if (resolvedDs == null) { + return null; + } + return new DatasourceOperations(resolvedDs, relationalJdbcConfiguration); } catch (SQLException sqlException) { throw new RuntimeException(sqlException); } - return databaseOperations; } @Override diff --git a/site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md b/site/content/in-dev/unreleased/proposals/jdbc-multi-datasource.md similarity index 100% rename from site/content/in-dev/unreleased/metastores/jdbc-multi-datasource.md rename to site/content/in-dev/unreleased/proposals/jdbc-multi-datasource.md