This is an automated email from the ASF dual-hosted git repository.
singhpk234 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 8f9ff62a99 RELATIONAL-JDBC: Add support for cockroach DB (#3352)
8f9ff62a99 is described below
commit 8f9ff62a993e6fa990677d26a499676a01593291
Author: Prashant Singh <[email protected]>
AuthorDate: Mon Mar 9 11:23:51 2026 -0700
RELATIONAL-JDBC: Add support for cockroach DB (#3352)
CockroachDB Support:
- Added COCKROACHDB to DatabaseType enum (maps to "cockroachdb" directory)
- Created separate schema directory: cockroachdb/schema-v4.sql
- CockroachDB schema v4 based on PostgreSQL schema v4 (includes all tables)
- Uses INT4 explicitly for integer columns (required for CockroachDB JDBC
driver)
Database Type Detection and Validation:
- Implemented DatabaseType.inferFromConnection() to detect database from
JDBC metadata
- If database-type is configured: uses configured type and validates
against connection
- If configured type mismatches connection: throws IllegalStateException
with clear error
- If no configured type: auto-detects from connection (CockroachDB,
PostgreSQL, H2)
- Validation ensures configuration matches actual database
Configuration:
- New property: polaris.persistence.relational.jdbc.database-type
- Supported values: "postgresql", "cockroachdb", "h2"
- If set, configuration is authoritative and validated against connection
- If not set, auto-detects from JDBC connection metadata
---------
Co-authored-by: sathesuraj
Co-authored-by: Prashant Kumar Singh <[email protected]>
---
.../persistence/relational/jdbc/DatabaseType.java | 119 +++++++-
.../relational/jdbc/DatasourceOperations.java | 15 +-
.../relational/jdbc/JdbcBootstrapUtils.java | 26 +-
.../jdbc/JdbcMetaStoreManagerFactory.java | 1 +
.../jdbc/RelationalJdbcConfiguration.java | 6 +
.../src/main/resources/cockroachdb/schema-v4.sql | 308 +++++++++++++++++++++
...toreManagerWithJdbcBasePersistenceImplTest.java | 5 +
.../relational/jdbc/JdbcBootstrapUtilsTest.java | 46 ++-
.../jdbc/MetricsReportPersistenceTest.java | 5 +
.../RelationalJdbcIdempotencyStorePostgresIT.java | 5 +
.../relational/jdbc/CockroachJdbcAdminProfile.java | 47 ++++
.../jdbc/CockroachJdbcBootstrapCommandTest.java | 42 +++
.../jdbc/CockroachJdbcPurgeCommandTest.java | 27 +-
.../cockroach/CockroachApplicationIT.java | 20 +-
.../cockroach/CockroachManagementServiceIT.java | 20 +-
.../cockroach/CockroachPolicyServiceIT.java | 20 +-
.../cockroach/CockroachRestCatalogIT.java | 20 +-
.../relational/cockroach/CockroachViewFileIT.java | 20 +-
runtime/test-common/build.gradle.kts | 1 +
...CockroachRelationalJdbcLifeCycleManagement.java | 93 +++++++
.../commons/CockroachRelationalJdbcProfile.java | 29 +-
.../test/commons/Dockerfile-cockroachdb-version | 14 +-
...smallrye-polaris_persistence_relational_jdbc.md | 1 +
23 files changed, 771 insertions(+), 119 deletions(-)
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 71a33db211..cc104e1f39 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
@@ -19,10 +19,21 @@
package org.apache.polaris.persistence.relational.jdbc;
import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.SQLException;
import java.util.Locale;
+/**
+ * Database types supported by Polaris relational JDBC persistence.
+ *
+ * <p>CockroachDB uses the PostgreSQL wire protocol but has its own schema
resources in the
+ * "cockroachdb" directory. While CockroachDB is PostgreSQL-compatible, having
separate schemas
+ * allows for CockroachDB-specific optimizations and avoids experimental ALTER
operations that
+ * CockroachDB doesn't fully support.
+ */
public enum DatabaseType {
POSTGRES("postgres"),
+ COCKROACHDB("cockroachdb"),
H2("h2");
private final String displayName; // Store the user-friendly name
@@ -36,28 +47,128 @@ public enum DatabaseType {
return displayName;
}
+ /**
+ * Returns the latest schema version available for this database type. This
is used as the default
+ * schema version for new installations.
+ */
+ public int getLatestSchemaVersion() {
+ return switch (this) {
+ case POSTGRES -> 4; // PostgreSQL has schemas v1, v2, v3, v4
+ case COCKROACHDB -> 4; // CockroachDB schema version kept in sync with
PostgreSQL
+ case H2 -> 4; // H2 uses same schemas as PostgreSQL
+ };
+ }
+
public static DatabaseType fromDisplayName(String displayName) {
return switch (displayName.toLowerCase(Locale.ROOT)) {
case "h2" -> DatabaseType.H2;
case "postgresql" -> DatabaseType.POSTGRES;
+ case "cockroachdb" -> DatabaseType.COCKROACHDB;
default -> throw new IllegalStateException("Unsupported DatabaseType: '"
+ displayName + "'");
};
}
+ /**
+ * Determines the database type from JDBC connection metadata and validates
against configured
+ * type.
+ *
+ * <p>Logic:
+ *
+ * <ul>
+ * <li>If configuredType is provided: uses it as the authoritative type
and validates it matches
+ * what the connection reports. Throws an exception if there's a
mismatch.
+ * <li>If configuredType is null: infers the type from connection metadata.
+ * </ul>
+ *
+ * @param connection JDBC connection to inspect
+ * @param configuredType Explicitly configured database type (may be null)
+ * @return The database type (configured if provided, otherwise inferred)
+ * @throws IllegalStateException if configured type doesn't match connection
metadata
+ */
+ public static DatabaseType inferFromConnection(
+ Connection connection, DatabaseType configuredType) {
+ try {
+ var metaData = connection.getMetaData();
+ String productName =
metaData.getDatabaseProductName().toLowerCase(Locale.ROOT);
+
+ // Infer database type from connection metadata
+ DatabaseType inferredType = null;
+
+ if (productName.contains("cockroach")) {
+ inferredType = DatabaseType.COCKROACHDB;
+ } else if (productName.contains("postgresql")) {
+ inferredType = DatabaseType.POSTGRES;
+ } else if (productName.contains("h2")) {
+ inferredType = DatabaseType.H2;
+ }
+
+ // If a type was explicitly configured, use it and validate
+ if (configuredType != null) {
+ if (inferredType != null && inferredType != configuredType) {
+ // Special case: CockroachDB uses PostgreSQL JDBC driver and wire
protocol
+ // So configured=COCKROACHDB with inferred=POSTGRES is valid
+ boolean isValidCockroachConfig =
+ configuredType == DatabaseType.COCKROACHDB && inferredType ==
DatabaseType.POSTGRES;
+
+ if (!isValidCockroachConfig) {
+ throw new IllegalStateException(
+ String.format(
+ "Configured database type '%s' does not match detected
type '%s' from connection (product: '%s'). "
+ + "Please check your
polaris.persistence.relational.jdbc.database-type configuration.",
+ configuredType, inferredType, productName));
+ }
+ }
+ // Use configured type (either it matches, it's valid CockroachDB, or
we couldn't infer)
+ return configuredType;
+ }
+
+ // No configured type - use inferred type
+ if (inferredType != null) {
+ return inferredType;
+ }
+
+ // Couldn't infer and no configured type
+ throw new IllegalStateException(
+ "Cannot infer database type from product name: '"
+ + productName
+ + "'. "
+ + "Please set polaris.persistence.relational.jdbc.database-type
explicitly.");
+
+ } catch (SQLException e) {
+ // If we can't get metadata, must have a configured type
+ if (configuredType != null) {
+ return configuredType;
+ }
+ throw new IllegalStateException(
+ "Cannot determine database type: unable to read connection
metadata", e);
+ }
+ }
+
/**
* Open an InputStream that contains data from an init script. This stream
should be closed by the
* caller.
*/
public InputStream openInitScriptResource(int schemaVersion) {
- // Preconditions check is simpler and more direct than a switch default
- if (schemaVersion <= 0 || schemaVersion > 4) {
- throw new IllegalArgumentException("Unknown or invalid schema version "
+ schemaVersion);
+ // Validate schema version is within acceptable range for this database
type
+ int latestVersion = getLatestSchemaVersion();
+ if (schemaVersion <= 0 || schemaVersion > latestVersion) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Invalid schema version %d for database type %s. Valid range:
1-%d",
+ schemaVersion, this, latestVersion));
}
final String resourceName =
String.format("%s/schema-v%d.sql", this.getDisplayName(),
schemaVersion);
ClassLoader classLoader = DatasourceOperations.class.getClassLoader();
- return classLoader.getResourceAsStream(resourceName);
+ InputStream stream = classLoader.getResourceAsStream(resourceName);
+ if (stream == null) {
+ throw new IllegalStateException(
+ String.format(
+ "Schema resource not found: %s (database type: %s, version: %d)",
+ resourceName, this, schemaVersion));
+ }
+ return stream;
}
}
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 5e1b2785d9..4006ef28af 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
@@ -76,8 +76,17 @@ public class DatasourceOperations {
this.datasource = datasource;
this.relationalJdbcConfiguration = relationalJdbcConfiguration;
try (Connection connection = this.datasource.getConnection()) {
- String productName = connection.getMetaData().getDatabaseProductName();
- this.databaseType = DatabaseType.fromDisplayName(productName);
+ // Get explicitly configured database type, if any
+ DatabaseType configuredType =
+ relationalJdbcConfiguration
+ .databaseType()
+ .map(DatabaseType::fromDisplayName)
+ .orElse(null);
+
+ // Infer database type from connection, falling back to configured type
+ this.databaseType = DatabaseType.inferFromConnection(connection,
configuredType);
+
+ LOGGER.info("Detected database type: {}", databaseType);
}
}
@@ -403,7 +412,7 @@ public class DatasourceOperations {
public boolean isRelationDoesNotExist(SQLException e) {
return (RELATION_DOES_NOT_EXIST.equals(e.getSQLState())
- && databaseType == DatabaseType.POSTGRES)
+ && (databaseType == DatabaseType.POSTGRES || databaseType ==
DatabaseType.COCKROACHDB))
|| ((H2_SCHEMA_DOES_NOT_EXIST.equals(e.getSQLState())
|| H2_TABLE_DOES_NOT_EXIST.equals(e.getSQLState()))
&& databaseType == DatabaseType.H2);
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 814417d1b8..3e0391ca79 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
@@ -30,6 +30,7 @@ public class JdbcBootstrapUtils {
/**
* Determines the correct schema version to use for bootstrapping a realm.
*
+ * @param databaseType The database type being used (determines latest
available schema version).
* @param currentSchemaVersion The current version of the database schema.
* @param requiredSchemaVersion The requested schema version (-1 for
auto-detection).
* @param hasAlreadyBootstrappedRealms Flag indicating if any realms already
exist.
@@ -37,7 +38,12 @@ public class JdbcBootstrapUtils {
* @throws IllegalStateException if the combination of parameters represents
an invalid state.
*/
public static int getRealmBootstrapSchemaVersion(
- int currentSchemaVersion, int requiredSchemaVersion, boolean
hasAlreadyBootstrappedRealms) {
+ DatabaseType databaseType,
+ int currentSchemaVersion,
+ int requiredSchemaVersion,
+ boolean hasAlreadyBootstrappedRealms) {
+
+ int latestSchemaVersion = databaseType.getLatestSchemaVersion();
// If versions already match, no change is needed.
if (currentSchemaVersion == requiredSchemaVersion) {
@@ -52,23 +58,27 @@ public class JdbcBootstrapUtils {
return 1;
}
} else {
- // A truly fresh start. Default to v3 for auto-detection, otherwise
use the specified
- // version.
- return requiredSchemaVersion == -1 ? 3 : requiredSchemaVersion;
+ // 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;
}
}
// Handle auto-detection on an existing installation (current version > 0).
if (requiredSchemaVersion == -1) {
- // Use the current version if realms already exist; otherwise, use v3
for the new realm.
- return hasAlreadyBootstrappedRealms ? currentSchemaVersion : 3;
+ // Use the current version if realms already exist; otherwise, use
latest version for the new
+ // realm.
+ return hasAlreadyBootstrappedRealms ? currentSchemaVersion :
latestSchemaVersion;
}
// Any other combination is an unhandled or invalid migration path.
throw new IllegalStateException(
String.format(
- "Cannot determine bootstrap schema version. Current: %d, Required:
%d, Bootstrapped: %b",
- currentSchemaVersion, requiredSchemaVersion,
hasAlreadyBootstrappedRealms));
+ "Cannot determine bootstrap schema version. Current: %d, Required:
%d, Bootstrapped: %b, Database: %s",
+ currentSchemaVersion,
+ requiredSchemaVersion,
+ hasAlreadyBootstrappedRealms,
+ databaseType));
}
/**
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 58947bd182..032934bae1 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
@@ -160,6 +160,7 @@ public class JdbcMetaStoreManagerFactory implements
MetaStoreManagerFactory {
int requestedSchemaVersion =
JdbcBootstrapUtils.getRequestedSchemaVersion(bootstrapOptions);
int effectiveSchemaVersion =
JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ datasourceOperations.getDatabaseType(),
currentSchemaVersion,
requestedSchemaVersion,
JdbcBasePersistenceImpl.entityTableExists(datasourceOperations));
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 22e389a086..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
@@ -29,4 +29,10 @@ public interface RelationalJdbcConfiguration {
// initial delay
Optional<Long> initialDelayInMs();
+
+ /**
+ * Explicitly configured database type. If not specified, the database type
will be inferred from
+ * the JDBC connection metadata. Supported values: "postgresql",
"cockroachdb", "h2"
+ */
+ Optional<String> databaseType();
}
diff --git
a/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4.sql
b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4.sql
new file mode 100644
index 0000000000..b982bae991
--- /dev/null
+++ b/persistence/relational-jdbc/src/main/resources/cockroachdb/schema-v4.sql
@@ -0,0 +1,308 @@
+--
+-- 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.
+
+-- CockroachDB schema v4 (matching PostgreSQL schema v4)
+-- Schema version is kept in sync with PostgreSQL to ensure correct column
selection in ModelEntity.
+-- Changes from v3:
+-- * Added `idempotency_records` table for REST idempotency
+-- * Added `scan_metrics_report` table
+-- * Added `commit_metrics_report` table
+-- Features:
+-- * Uses INT4 explicitly for all integer columns (required for CockroachDB
JDBC driver)
+-- * Includes all tables: version, entities, grant_records,
principal_authentication_data,
+-- policy_mapping_record, events, idempotency_records, scan_metrics_report,
commit_metrics_report
+-- * Compatible with PostgreSQL wire protocol
+
+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)
+);
+
+-- TODO: create indexes based on all query pattern.
+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';
+
+COMMENT ON COLUMN entities.realm_id IS 'realm_id used for multi-tenancy';
+COMMENT ON COLUMN entities.catalog_id IS 'catalog id';
+COMMENT ON COLUMN entities.id IS 'entity id';
+COMMENT ON COLUMN entities.parent_id IS 'entity id of parent';
+COMMENT ON COLUMN entities.name IS 'entity name';
+COMMENT ON COLUMN entities.entity_version IS 'version of the entity';
+COMMENT ON COLUMN entities.type_code IS 'type code';
+COMMENT ON COLUMN entities.sub_type_code IS 'sub type of entity';
+COMMENT ON COLUMN entities.create_timestamp IS 'creation time of entity';
+COMMENT ON COLUMN entities.drop_timestamp IS 'time of drop of entity';
+COMMENT ON COLUMN entities.purge_timestamp IS 'time to start purging entity';
+COMMENT ON COLUMN entities.last_update_timestamp IS 'last time the entity is
touched';
+COMMENT ON COLUMN entities.properties IS 'entities properties json';
+COMMENT ON COLUMN entities.internal_properties IS 'entities internal
properties json';
+COMMENT ON COLUMN entities.grant_records_version IS 'the version of grant
records change on the entity';
+
+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)
+);
+
+COMMENT ON TABLE grant_records IS 'grant records for entities';
+
+COMMENT ON COLUMN grant_records.securable_catalog_id IS 'catalog id of the
securable';
+COMMENT ON COLUMN grant_records.securable_id IS 'entity id of the securable';
+COMMENT ON COLUMN grant_records.grantee_catalog_id IS 'catalog id of the
grantee';
+COMMENT ON COLUMN grant_records.grantee_id IS 'id of the grantee';
+COMMENT ON COLUMN grant_records.privilege_code IS '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)
+);
+
+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 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 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)
+);
+
+-- Idempotency records (key-only idempotency; durable replay)
+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, -- normalized request-derived
resource identifier (not a generated entity id)
+
+ -- Finalization/replay
+ http_status INT4, -- NULL while IN_PROGRESS; set only
on finalized 2xx/terminal 4xx
+ error_subtype TEXT, -- optional: e.g., already_exists,
namespace_not_empty, idempotency_replay_failed
+ response_summary TEXT, -- minimal body to reproduce
equivalent response (JSON string)
+ response_headers TEXT, -- small whitelisted headers to
replay (JSON string)
+ finalized_at TIMESTAMP, -- when http_status was written
+
+ -- Liveness/ops
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL,
+ heartbeat_at TIMESTAMP, -- updated by owner while IN_PROGRESS
+ executor_id TEXT, -- owner pod/worker id
+ expires_at TIMESTAMP,
+
+ PRIMARY KEY (realm_id, idempotency_key)
+);
+
+-- Helpful indexes
+CREATE INDEX IF NOT EXISTS idx_idemp_realm_expires
+ ON idempotency_records (realm_id, expires_at);
+
+-- ============================================================================
+-- SCAN METRICS REPORT TABLE
+-- ============================================================================
+
+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,
+
+ -- Report metadata
+ timestamp_ms BIGINT NOT NULL,
+ principal_name TEXT,
+ request_id TEXT,
+
+ -- Trace correlation
+ otel_trace_id TEXT,
+ otel_span_id TEXT,
+ report_trace_id TEXT,
+
+ -- Scan context
+ snapshot_id BIGINT,
+ schema_id INT4,
+ filter_expression TEXT,
+ projected_field_ids TEXT,
+ projected_field_names TEXT,
+
+ -- Scan metrics
+ 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/positional delete metrics
+ 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,
+
+ -- Additional metadata (for extensibility)
+ metadata JSONB DEFAULT '{}'::JSONB,
+
+ PRIMARY KEY (realm_id, report_id)
+);
+
+COMMENT ON TABLE scan_metrics_report IS 'Scan metrics reports as first-class
entities';
+
+-- Index for retention cleanup by timestamp
+CREATE INDEX IF NOT EXISTS idx_scan_report_timestamp ON
scan_metrics_report(realm_id, timestamp_ms);
+
+-- Index for query lookups by catalog_id and table_id
+CREATE INDEX IF NOT EXISTS idx_scan_report_lookup ON
scan_metrics_report(realm_id, catalog_id, table_id, timestamp_ms);
+
+-- ============================================================================
+-- COMMIT METRICS REPORT TABLE
+-- ============================================================================
+
+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,
+
+ -- Report metadata
+ timestamp_ms BIGINT NOT NULL,
+ principal_name TEXT,
+ request_id TEXT,
+
+ -- Trace correlation
+ otel_trace_id TEXT,
+ otel_span_id TEXT,
+ report_trace_id TEXT,
+
+ -- Commit context
+ snapshot_id BIGINT NOT NULL,
+ sequence_number BIGINT,
+ operation TEXT NOT NULL,
+
+ -- File metrics
+ 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,
+
+ -- Equality delete files
+ added_equality_delete_files BIGINT DEFAULT 0,
+ removed_equality_delete_files BIGINT DEFAULT 0,
+
+ -- Positional delete files
+ added_positional_delete_files BIGINT DEFAULT 0,
+ removed_positional_delete_files BIGINT DEFAULT 0,
+
+ -- Record metrics
+ added_records BIGINT DEFAULT 0,
+ removed_records BIGINT DEFAULT 0,
+ total_records BIGINT DEFAULT 0,
+
+ -- Size metrics
+ added_file_size_bytes BIGINT DEFAULT 0,
+ removed_file_size_bytes BIGINT DEFAULT 0,
+ total_file_size_bytes BIGINT DEFAULT 0,
+
+ -- Duration and attempts
+ total_duration_ms BIGINT DEFAULT 0,
+ attempts INT4 DEFAULT 1,
+
+ -- Additional metadata (for extensibility)
+ metadata JSONB DEFAULT '{}'::JSONB,
+
+ PRIMARY KEY (realm_id, report_id)
+);
+
+COMMENT ON TABLE commit_metrics_report IS 'Commit metrics reports as
first-class entities';
+
+-- Index for retention cleanup by timestamp
+CREATE INDEX IF NOT EXISTS idx_commit_report_timestamp ON
commit_metrics_report(realm_id, timestamp_ms);
+
+-- Index for query lookups by catalog_id and table_id
+CREATE INDEX IF NOT EXISTS idx_commit_report_lookup ON
commit_metrics_report(realm_id, catalog_id, table_id, timestamp_ms);
+
+-- INT4 type used directly in table definitions for CockroachDB JDBC
compatibility
+-- CockroachDB requires explicit INT4 type declarations to correctly map
columns to Java's Integer type.
+-- Using generic INTEGER or INT types causes type mapping failures in
CockroachDB's JDBC driver.
+-- INT4 is equivalent to INTEGER in PostgreSQL, ensuring compatibility with
both databases.
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 dedce84198..ec1bc59691 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
@@ -95,5 +95,10 @@ public abstract class
AtomicMetastoreManagerWithJdbcBasePersistenceImplTest
public Optional<Long> initialDelayInMs() {
return Optional.of(100L);
}
+
+ @Override
+ public Optional<String> databaseType() {
+ return Optional.of("h2");
+ }
}
}
diff --git
a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtilsTest.java
b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtilsTest.java
index 6a9eb95524..671baf8ac2 100644
---
a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtilsTest.java
+++
b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/JdbcBootstrapUtilsTest.java
@@ -44,9 +44,13 @@ class JdbcBootstrapUtilsTest {
// Act & Assert
assertEquals(
- version, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(version,
version, true));
+ version,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, version, version, true));
assertEquals(
- version, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(version,
version, false));
+ version,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, version, version, false));
}
@Test
@@ -57,11 +61,17 @@ class JdbcBootstrapUtilsTest {
// Act & Assert
assertEquals(
- 3, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion,
-1, hasRealms));
+ 4,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, -1, hasRealms));
assertEquals(
- 2, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion,
2, hasRealms));
+ 2,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, 2, hasRealms));
assertEquals(
- 3, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion,
3, hasRealms));
+ 3,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, 3, hasRealms));
}
@Test
@@ -72,19 +82,31 @@ class JdbcBootstrapUtilsTest {
// Act & Assert
assertEquals(
- 1, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion,
-1, hasRealms));
+ 1,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, -1, hasRealms));
assertEquals(
- 1, JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion,
1, hasRealms));
+ 1,
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, 1, hasRealms));
}
@ParameterizedTest
- @CsvSource({"2, true, 2", "3, true, 3", "2, false, 3", "3, false, 3"})
+ @CsvSource({
+ "2, true, 2",
+ "3, true, 3",
+ "4, true, 4",
+ "2, false, 4",
+ "3, false, 4",
+ "4, false, 4"
+ })
void getVersion_whenExistingDbAndAutoDetect(
int currentVersion, boolean hasRealms, int expectedVersion) {
// Act & Assert
assertEquals(
expectedVersion,
- JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(currentVersion, -1,
hasRealms));
+ JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
+ DatabaseType.POSTGRES, currentVersion, -1, hasRealms));
}
@Test
@@ -99,7 +121,7 @@ class JdbcBootstrapUtilsTest {
IllegalStateException.class,
() ->
JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
- currentVersion, invalidRequiredVersion, hasRealms));
+ DatabaseType.POSTGRES, currentVersion, invalidRequiredVersion,
hasRealms));
}
@Test
@@ -113,13 +135,13 @@ class JdbcBootstrapUtilsTest {
IllegalStateException.class,
() ->
JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
- currentVersion, requiredVersion, true));
+ DatabaseType.POSTGRES, currentVersion, requiredVersion, true));
assertThrows(
IllegalStateException.class,
() ->
JdbcBootstrapUtils.getRealmBootstrapSchemaVersion(
- currentVersion, requiredVersion, false));
+ DatabaseType.POSTGRES, currentVersion, requiredVersion,
false));
}
@Nested
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 5d0e3ac144..f2ae29972f 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
@@ -197,5 +197,10 @@ class MetricsReportPersistenceTest {
public Optional<Long> initialDelayInMs() {
return Optional.of(10L);
}
+
+ @Override
+ public Optional<String> databaseType() {
+ return Optional.empty();
+ }
}
}
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 82cbfc9bea..ee95671dcd 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
@@ -77,6 +77,11 @@ public class RelationalJdbcIdempotencyStorePostgresIT {
public Optional<Long> initialDelayInMs() {
return Optional.of(100L);
}
+
+ @Override
+ public Optional<String> databaseType() {
+ return Optional.empty();
+ }
};
DatasourceOperations ops = new DatasourceOperations(dataSource, cfg);
try (InputStream is =
diff --git
a/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcAdminProfile.java
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcAdminProfile.java
new file mode 100644
index 0000000000..2b2e1d4d97
--- /dev/null
+++
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcAdminProfile.java
@@ -0,0 +1,47 @@
+/*
+ * 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.admintool.relational.jdbc;
+
+import java.util.List;
+import java.util.Map;
+import
org.apache.polaris.test.commons.CockroachRelationalJdbcLifeCycleManagement;
+import org.apache.polaris.test.commons.RelationalJdbcProfile;
+
+public class CockroachJdbcAdminProfile extends RelationalJdbcProfile {
+ @Override
+ public Map<String, String> getConfigOverrides() {
+ return Map.of(
+ "polaris.persistence.type",
+ "relational-jdbc",
+ "polaris.persistence.relational.jdbc.database-type",
+ "cockroachdb",
+ // These two options are required to "trigger" the enablement of JDBC
data sources in
+ // admin-tool tests, but do not harm other tests.
+ "quarkus.datasource.active",
+ "true",
+ "quarkus.datasource.db-kind",
+ "postgresql");
+ }
+
+ @Override
+ public List<TestResourceEntry> testResources() {
+ return List.of(
+ new
TestResourceEntry(CockroachRelationalJdbcLifeCycleManagement.class, Map.of()));
+ }
+}
diff --git
a/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcBootstrapCommandTest.java
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcBootstrapCommandTest.java
new file mode 100644
index 0000000000..c9fd056d6f
--- /dev/null
+++
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcBootstrapCommandTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.admintool.relational.jdbc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.test.junit.TestProfile;
+import io.quarkus.test.junit.main.LaunchResult;
+import io.quarkus.test.junit.main.QuarkusMainLauncher;
+import org.junit.jupiter.api.Test;
+
+@TestProfile(CockroachJdbcAdminProfile.class)
+public class CockroachJdbcBootstrapCommandTest extends
RelationalJdbcBootstrapCommandTest {
+
+ @Override
+ @Test
+ public void testBootstrapFailsWhenAddingRealmWithDifferentSchemaVersion(
+ QuarkusMainLauncher launcher) {
+ // CockroachDB only has schema v4 (no v1, v2 or v3 schemas exist).
+ // Override to bootstrap with v4, which is the only version available for
CockroachDB.
+ LaunchResult result1 =
+ launcher.launch("bootstrap", "-v", "4", "-r", "realm1", "-c",
"realm1,root,s3cr3t");
+ assertThat(result1.exitCode()).isEqualTo(0);
+ assertThat(result1.getOutput()).contains("Bootstrap completed
successfully.");
+ }
+}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcPurgeCommandTest.java
similarity index 55%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcPurgeCommandTest.java
index 22e389a086..550eb779f4 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/admin/src/test/java/org/apache/polaris/admintool/relational/jdbc/CockroachJdbcPurgeCommandTest.java
@@ -16,17 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.admintool.relational.jdbc;
-import java.util.Optional;
+import com.google.common.collect.ImmutableMap;
+import io.quarkus.test.junit.TestProfile;
+import java.util.Map;
+import org.apache.polaris.admintool.PurgeCommandTestBase;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
+@TestProfile(CockroachJdbcPurgeCommandTest.Profile.class)
+public class CockroachJdbcPurgeCommandTest extends PurgeCommandTestBase {
+ public static class Profile extends CockroachJdbcAdminProfile {
+ @Override
+ public Map<String, String> getConfigOverrides() {
+ return ImmutableMap.<String, String>builder()
+ .putAll(super.getConfigOverrides())
+ .put("pre-bootstrap", "true")
+ .build();
+ }
+ }
}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachApplicationIT.java
similarity index 63%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachApplicationIT.java
index 22e389a086..2909b7cd5b 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachApplicationIT.java
@@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.service.it.relational.cockroach;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import org.apache.polaris.service.it.test.PolarisApplicationIntegrationTest;
+import org.apache.polaris.test.commons.CockroachRelationalJdbcProfile;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
-}
+@TestProfile(CockroachRelationalJdbcProfile.class)
+@QuarkusIntegrationTest
+public class CockroachApplicationIT extends PolarisApplicationIntegrationTest
{}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachManagementServiceIT.java
similarity index 62%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachManagementServiceIT.java
index 22e389a086..318b012234 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachManagementServiceIT.java
@@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.service.it.relational.cockroach;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import
org.apache.polaris.service.it.test.PolarisManagementServiceIntegrationTest;
+import org.apache.polaris.test.commons.CockroachRelationalJdbcProfile;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
-}
+@TestProfile(CockroachRelationalJdbcProfile.class)
+@QuarkusIntegrationTest
+public class CockroachManagementServiceIT extends
PolarisManagementServiceIntegrationTest {}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachPolicyServiceIT.java
similarity index 63%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachPolicyServiceIT.java
index 22e389a086..c044c6fdfe 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachPolicyServiceIT.java
@@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.service.it.relational.cockroach;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import org.apache.polaris.service.it.test.PolarisPolicyServiceIntegrationTest;
+import org.apache.polaris.test.commons.CockroachRelationalJdbcProfile;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
-}
+@TestProfile(CockroachRelationalJdbcProfile.class)
+@QuarkusIntegrationTest
+public class CockroachPolicyServiceIT extends
PolarisPolicyServiceIntegrationTest {}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachRestCatalogIT.java
similarity index 63%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachRestCatalogIT.java
index 22e389a086..a8fa72c14c 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachRestCatalogIT.java
@@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.service.it.relational.cockroach;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import
org.apache.polaris.service.it.test.PolarisRestCatalogFileIntegrationTest;
+import org.apache.polaris.test.commons.CockroachRelationalJdbcProfile;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
-}
+@TestProfile(CockroachRelationalJdbcProfile.class)
+@QuarkusIntegrationTest
+public class CockroachRestCatalogIT extends
PolarisRestCatalogFileIntegrationTest {}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachViewFileIT.java
similarity index 62%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachViewFileIT.java
index 22e389a086..80b0cd97d7 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/relational/cockroach/CockroachViewFileIT.java
@@ -16,17 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.service.it.relational.cockroach;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.quarkus.test.junit.TestProfile;
+import
org.apache.polaris.service.it.test.PolarisRestCatalogViewFileIntegrationTestBase;
+import org.apache.polaris.test.commons.CockroachRelationalJdbcProfile;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
-
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
-}
+@TestProfile(CockroachRelationalJdbcProfile.class)
+@QuarkusIntegrationTest
+public class CockroachViewFileIT extends
PolarisRestCatalogViewFileIntegrationTestBase {}
diff --git a/runtime/test-common/build.gradle.kts
b/runtime/test-common/build.gradle.kts
index 454040c815..ff22995eca 100644
--- a/runtime/test-common/build.gradle.kts
+++ b/runtime/test-common/build.gradle.kts
@@ -41,6 +41,7 @@ dependencies {
implementation(platform(libs.testcontainers.bom))
implementation("org.testcontainers:testcontainers")
implementation("org.testcontainers:testcontainers-postgresql")
+ implementation("org.testcontainers:testcontainers-cockroachdb")
implementation(libs.testcontainers.keycloak) {
exclude(group = "org.keycloak", module = "keycloak-admin-client")
diff --git
a/runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcLifeCycleManagement.java
b/runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcLifeCycleManagement.java
new file mode 100644
index 0000000000..6a1577c7ca
--- /dev/null
+++
b/runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcLifeCycleManagement.java
@@ -0,0 +1,93 @@
+/*
+ * 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.test.commons;
+
+import static
org.apache.polaris.containerspec.ContainerSpecHelper.containerSpecHelper;
+
+import io.quarkus.test.common.DevServicesContext;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import java.util.Map;
+import org.testcontainers.cockroachdb.CockroachContainer;
+
+public class CockroachRelationalJdbcLifeCycleManagement
+ implements QuarkusTestResourceLifecycleManager,
DevServicesContext.ContextAware {
+ public static final String INIT_SCRIPT = "init-script";
+
+ private CockroachContainer cockroach;
+ private String initScript;
+ private DevServicesContext context;
+
+ @Override
+ public void init(Map<String, String> initArgs) {
+ initScript = initArgs.get(INIT_SCRIPT);
+ }
+
+ @Override
+ @SuppressWarnings("resource")
+ public Map<String, String> start() {
+ cockroach =
+ new CockroachContainer(
+ containerSpecHelper("cockroachdb",
CockroachRelationalJdbcLifeCycleManagement.class)
+ .dockerImageName(null)
+ .asCompatibleSubstituteFor("cockroachdb/cockroach"));
+
+ if (initScript != null) {
+ cockroach.withInitScript(initScript);
+ }
+
+ context.containerNetworkId().ifPresent(cockroach::withNetworkMode);
+ cockroach.start();
+
+ // CockroachDB uses PostgreSQL JDBC driver and wire protocol
+ // Explicitly configure database type as cockroachdb for proper
identification
+ return Map.of(
+ "polaris.persistence.type",
+ "relational-jdbc",
+ "polaris.persistence.relational.jdbc.database-type",
+ "cockroachdb",
+ "polaris.persistence.relational.jdbc.max-retries",
+ "2",
+ "quarkus.datasource.db-kind",
+ "postgresql",
+ "quarkus.datasource.jdbc.url",
+ cockroach.getJdbcUrl(),
+ "quarkus.datasource.username",
+ cockroach.getUsername(),
+ "quarkus.datasource.password",
+ cockroach.getPassword(),
+ "quarkus.datasource.jdbc.initial-size",
+ "10");
+ }
+
+ @Override
+ public void stop() {
+ if (cockroach != null) {
+ try {
+ cockroach.stop();
+ } finally {
+ cockroach = null;
+ }
+ }
+ }
+
+ @Override
+ public void setIntegrationTestContext(DevServicesContext context) {
+ this.context = context;
+ }
+}
diff --git
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
b/runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcProfile.java
similarity index 54%
copy from
persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
copy to
runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcProfile.java
index 22e389a086..b86a0209b5 100644
---
a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/RelationalJdbcConfiguration.java
+++
b/runtime/test-common/src/main/java/org/apache/polaris/test/commons/CockroachRelationalJdbcProfile.java
@@ -16,17 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.persistence.relational.jdbc;
+package org.apache.polaris.test.commons;
-import java.util.Optional;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import java.util.List;
+import java.util.Map;
-public interface RelationalJdbcConfiguration {
- // max retries before giving up
- Optional<Integer> maxRetries();
+public class CockroachRelationalJdbcProfile implements QuarkusTestProfile {
+ @Override
+ public Map<String, String> getConfigOverrides() {
+ return Map.of(
+ "polaris.persistence.auto-bootstrap-types",
+ "relational-jdbc",
+ "polaris.persistence.relational.jdbc.database-type",
+ "cockroachdb");
+ }
- // max retry duration
- Optional<Long> maxDurationInMs();
-
- // initial delay
- Optional<Long> initialDelayInMs();
+ @Override
+ public List<TestResourceEntry> testResources() {
+ return List.of(
+ new QuarkusTestProfile.TestResourceEntry(
+ CockroachRelationalJdbcLifeCycleManagement.class, Map.of()));
+ }
}
diff --git
a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md
b/runtime/test-common/src/main/resources/org/apache/polaris/test/commons/Dockerfile-cockroachdb-version
similarity index 65%
copy from
site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md
copy to
runtime/test-common/src/main/resources/org/apache/polaris/test/commons/Dockerfile-cockroachdb-version
index 08f309567d..b74de1108c 100644
---
a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_persistence_relational_jdbc.md
+++
b/runtime/test-common/src/main/resources/org/apache/polaris/test/commons/Dockerfile-cockroachdb-version
@@ -1,4 +1,3 @@
----
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -17,14 +16,7 @@
# specific language governing permissions and limitations
# under the License.
#
-title: smallrye-polaris_persistence_relational_jdbc
-build:
- list: never
- render: never
----
-| Property | Default Value | Type | Description |
-|----------|---------------|------|-------------|
-| `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` | |
+# Dockerfile to provide the image name and tag to a test.
+# Version is managed by Renovate - do not edit.
+FROM cockroachdb/cockroach:v24.3.0
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 08f309567d..e50f686542 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
@@ -28,3 +28,4 @@ build:
| `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` | |
+| `polaris.persistence.relational.jdbc.database-type` | | `string` | |