Skip to content

Commit 6d6dcbd

Browse files
committed
[JDBC] Part3: Plumb JDBC module to quarkus
1 parent 56c9d1d commit 6d6dcbd

File tree

16 files changed

+294
-26
lines changed

16 files changed

+294
-26
lines changed

extension/persistence/relational-jdbc/build.gradle.kts

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
* under the License.
1818
*/
1919

20-
plugins { id("polaris-server") }
20+
plugins {
21+
id("polaris-server")
22+
alias(libs.plugins.jandex)
23+
}
2124

2225
dependencies {
2326
implementation(project(":polaris-core"))
@@ -28,8 +31,6 @@ dependencies {
2831
compileOnly(libs.jakarta.inject.api)
2932

3033
implementation(libs.smallrye.common.annotation) // @Identifier
31-
32-
runtimeOnly(libs.postgresql)
3334
testImplementation(libs.mockito.junit.jupiter)
3435

3536
testImplementation(libs.h2)

extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/DatasourceOperations.java

+39-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.sql.SQLException;
2929
import java.sql.Statement;
3030
import java.util.List;
31+
import java.util.Locale;
3132
import java.util.Objects;
3233
import javax.sql.DataSource;
3334
import org.slf4j.Logger;
@@ -40,15 +41,23 @@ public class DatasourceOperations {
4041
private static final String CONSTRAINT_VIOLATION_SQL_CODE = "23505";
4142

4243
private final DataSource datasource;
44+
private final String realm;
45+
private final boolean isPostgres;
4346

44-
public DatasourceOperations(DataSource datasource) {
47+
public DatasourceOperations(DataSource datasource, String realm) {
4548
this.datasource = datasource;
49+
this.realm = realm.toUpperCase(Locale.ROOT);
50+
this.isPostgres = isPostgres();
4651
}
4752

4853
public void executeScript(String scriptFilePath) throws SQLException {
4954
ClassLoader classLoader = DatasourceOperations.class.getClassLoader();
50-
try (Connection connection = borrowConnection();
55+
Statement realmSchemaStatement = null;
56+
try (Connection connection = datasource.getConnection();
5157
Statement statement = connection.createStatement()) {
58+
statement.execute(String.format("CREATE SCHEMA IF NOT EXISTS %s", realm));
59+
statement.close();
60+
realmSchemaStatement = borrowConnection().createStatement();
5261
BufferedReader reader =
5362
new BufferedReader(
5463
new InputStreamReader(
@@ -62,7 +71,7 @@ public void executeScript(String scriptFilePath) throws SQLException {
6271
if (line.endsWith(";")) { // Execute statement when semicolon is found
6372
String sql = sqlBuffer.toString().trim();
6473
try {
65-
int rowsUpdated = statement.executeUpdate(sql);
74+
int rowsUpdated = realmSchemaStatement.executeUpdate(sql);
6675
LOGGER.debug("Query {} executed {} rows affected", sql, rowsUpdated);
6776
} catch (SQLException e) {
6877
LOGGER.error("Error executing query {}", sql, e);
@@ -79,6 +88,10 @@ public void executeScript(String scriptFilePath) throws SQLException {
7988
} catch (SQLException e) {
8089
LOGGER.error("Error executing the script file", e);
8190
throw e;
91+
} finally {
92+
if (realmSchemaStatement != null) {
93+
realmSchemaStatement.close();
94+
}
8295
}
8396
}
8497

@@ -151,7 +164,6 @@ public void runWithinTransaction(TransactionCallback callback) throws SQLExcepti
151164
connection.close();
152165
} catch (SQLException e) {
153166
LOGGER.error("Error closing connection", e);
154-
throw e;
155167
}
156168
}
157169
}
@@ -170,7 +182,29 @@ public boolean isAlreadyExistsException(SQLException e) {
170182
return ALREADY_EXISTS_STATE_POSTGRES.equals(e.getSQLState());
171183
}
172184

185+
private boolean isPostgres() {
186+
try (Connection connection = datasource.getConnection()) {
187+
return connection
188+
.getMetaData()
189+
.getDatabaseProductName()
190+
.toLowerCase(Locale.ROOT)
191+
.equals("postgresql");
192+
} catch (SQLException e) {
193+
return false;
194+
}
195+
}
196+
173197
private Connection borrowConnection() throws SQLException {
174-
return datasource.getConnection();
198+
Connection connection = datasource.getConnection();
199+
if (isPostgres) {
200+
// connection.setSchema() doesn't work hence work this around.
201+
Statement s = connection.createStatement();
202+
s.execute("SET search_path TO " + realm);
203+
s.close();
204+
} else {
205+
connection.setSchema(realm);
206+
}
207+
208+
return connection;
175209
}
176210
}

extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/JdbcCrudQueryGenerator.java

-3
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,6 @@ private static String getTableName(Class<?> entityClass) {
285285
throw new IllegalArgumentException("Unsupported entity class: " + entityClass.getName());
286286
}
287287

288-
// TODO: check if we want to make schema name configurable.
289-
tableName = "POLARIS_SCHEMA." + tableName;
290-
291288
return tableName;
292289
}
293290
}

extension/persistence/relational-jdbc/src/main/java/org/apache/polaris/extension/persistence/relational/jdbc/JdbcMetaStoreManagerFactory.java

+11-8
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
package org.apache.polaris.extension.persistence.relational.jdbc;
2020

2121
import io.smallrye.common.annotation.Identifier;
22-
import jakarta.annotation.Nonnull;
2322
import jakarta.enterprise.context.ApplicationScoped;
2423
import jakarta.inject.Inject;
2524
import java.sql.SQLException;
2625
import javax.sql.DataSource;
2726
import org.apache.polaris.core.PolarisDiagnostics;
2827
import org.apache.polaris.core.context.RealmContext;
29-
import org.apache.polaris.core.entity.*;
30-
import org.apache.polaris.core.persistence.*;
28+
import org.apache.polaris.core.persistence.AtomicOperationMetaStoreManager;
29+
import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory;
30+
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
3131
import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
3232
import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider;
3333
import org.slf4j.Logger;
@@ -45,15 +45,17 @@ public class JdbcMetaStoreManagerFactory extends LocalPolarisMetaStoreManagerFac
4545

4646
// TODO: Pending discussion of if we should have one Database per realm or 1 schema per realm
4747
// or realm should be a primary key on all the tables.
48-
@Inject DataSource dataSource;
48+
DataSource dataSource;
4949
@Inject PolarisStorageIntegrationProvider storageIntegrationProvider;
5050

5151
protected JdbcMetaStoreManagerFactory() {
52-
this(null);
52+
this(null, null);
5353
}
5454

55-
protected JdbcMetaStoreManagerFactory(@Nonnull PolarisDiagnostics diagnostics) {
55+
@Inject
56+
protected JdbcMetaStoreManagerFactory(PolarisDiagnostics diagnostics, DataSource dataSource) {
5657
super(diagnostics);
58+
this.dataSource = dataSource;
5759
}
5860

5961
/**
@@ -68,11 +70,12 @@ protected PolarisMetaStoreManager createNewMetaStoreManager() {
6870
@Override
6971
protected void initializeForRealm(
7072
RealmContext realmContext, RootCredentialsSet rootCredentialsSet) {
71-
DatasourceOperations databaseOperations = new DatasourceOperations(dataSource);
73+
DatasourceOperations databaseOperations =
74+
new DatasourceOperations(dataSource, realmContext.getRealmIdentifier());
7275
// TODO: see if we need to take script from Quarkus or can we just
7376
// use the script committed in the repo.
7477
try {
75-
databaseOperations.executeScript("scripts/postgres/schema-v1-postgres.sql");
78+
databaseOperations.executeScript("postgres/schema-v1-postgresql.sql");
7679
} catch (SQLException e) {
7780
throw new RuntimeException(
7881
String.format("Error executing sql script: %s", e.getMessage()), e);

extension/persistence/relational-jdbc/src/main/resources/h2/schema-v1-h2.sql

-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
-- under the License.
1818
--
1919

20-
CREATE SCHEMA IF NOT EXISTS POLARIS_SCHEMA;
21-
SET SCHEMA POLARIS_SCHEMA;
2220
DROP TABLE IF EXISTS entities;
2321
CREATE TABLE IF NOT EXISTS entities (
2422
catalog_id BIGINT NOT NULL,

scripts/postgres/schema-v1-postgresql.sql renamed to extension/persistence/relational-jdbc/src/main/resources/postgres/schema-v1-postgresql.sql

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ CREATE TABLE IF NOT EXISTS entities (
3737
purge_timestamp BIGINT NOT NULL,
3838
to_purge_timestamp BIGINT NOT NULL,
3939
last_update_timestamp BIGINT NOT NULL,
40-
properties JSONB not null default '{}'::JSONB,
41-
internal_properties JSONB not null default '{}'::JSONB,
40+
properties TEXT NOT NULL default '{}',
41+
internal_properties TEXT NOT NULL default '{}',
4242
grant_records_version INT NOT NULL,
4343
PRIMARY KEY (id),
4444
CONSTRAINT constraint_name UNIQUE (catalog_id, parent_id, type_code, name)

extension/persistence/relational-jdbc/src/test/java/org/apache/polaris/extension/persistence/impl/relational/jdbc/DatasourceOperationsTest.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static org.mockito.Mockito.when;
2727

2828
import java.sql.Connection;
29+
import java.sql.DatabaseMetaData;
2930
import java.sql.ResultSet;
3031
import java.sql.SQLException;
3132
import java.sql.Statement;
@@ -38,6 +39,7 @@
3839
import org.junit.jupiter.api.extension.ExtendWith;
3940
import org.mockito.Mock;
4041
import org.mockito.MockedStatic;
42+
import org.mockito.Mockito;
4143
import org.mockito.junit.jupiter.MockitoExtension;
4244

4345
@ExtendWith(MockitoExtension.class)
@@ -50,13 +52,17 @@ public class DatasourceOperationsTest {
5052

5153
@Mock private ResultSet mockResultSet;
5254

55+
@Mock private DatabaseMetaData mockDatabaseMetaData;
56+
5357
private DatasourceOperations datasourceOperations;
5458

5559
@BeforeEach
5660
void setUp() throws Exception {
5761
when(mockDataSource.getConnection()).thenReturn(mockConnection);
62+
when(mockConnection.getMetaData()).thenReturn(mockDatabaseMetaData);
63+
when(mockDatabaseMetaData.getDatabaseProductName()).thenReturn("H2");
5864
when(mockConnection.createStatement()).thenReturn(mockStatement);
59-
datasourceOperations = new DatasourceOperations(mockDataSource);
65+
datasourceOperations = new DatasourceOperations(mockDataSource, "realm");
6066
}
6167

6268
@Test
@@ -109,6 +115,7 @@ void testExecuteSelect_exception() throws Exception {
109115
void testRunWithinTransaction_commit() throws Exception {
110116
DatasourceOperations.TransactionCallback callback = statement -> true;
111117

118+
Mockito.reset(mockConnection);
112119
datasourceOperations.runWithinTransaction(callback);
113120

114121
verify(mockConnection).setAutoCommit(false);

extension/persistence/relational-jdbc/src/test/java/org/apache/polaris/extension/persistence/impl/relational/jdbc/JdbcAtomicMetastoreManagerTest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ public static DataSource createH2DataSource() {
4646
@Override
4747
protected PolarisTestMetaStoreManager createPolarisTestMetaStoreManager() {
4848
PolarisDiagnostics diagServices = new PolarisDefaultDiagServiceImpl();
49-
DatasourceOperations datasourceOperations = new DatasourceOperations(createH2DataSource());
49+
DatasourceOperations datasourceOperations =
50+
new DatasourceOperations(createH2DataSource(), "REALM");
5051
try {
5152
datasourceOperations.executeScript("h2/schema-v1-h2.sql");
5253
} catch (SQLException e) {
53-
throw new RuntimeException(String.format("Error executing h2 script: %s", e.getMessage()), e);
54+
throw new RuntimeException(String.format("Error executing h2 script: %s", e.getMessage()));
5455
}
5556

5657
JdbcBasePersistenceImpl basePersistence =

quarkus/admin/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ dependencies {
4444
implementation(project(":polaris-api-iceberg-service"))
4545

4646
runtimeOnly(project(":polaris-eclipselink"))
47+
runtimeOnly(project(":polaris-relational-jdbc"))
4748

49+
implementation("io.quarkus:quarkus-jdbc-postgresql")
4850
implementation(enforcedPlatform(libs.quarkus.bom))
4951
implementation("io.quarkus:quarkus-picocli")
5052
implementation("io.quarkus:quarkus-container-image-docker")

quarkus/admin/src/main/java/org/apache/polaris/admintool/config/QuarkusProducers.java

+9
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
*/
1919
package org.apache.polaris.admintool.config;
2020

21+
import io.quarkus.agroal.DataSource;
2122
import io.smallrye.common.annotation.Identifier;
2223
import jakarta.annotation.Nullable;
2324
import jakarta.enterprise.context.ApplicationScoped;
2425
import jakarta.enterprise.inject.Any;
2526
import jakarta.enterprise.inject.Instance;
2627
import jakarta.enterprise.inject.Produces;
28+
import jakarta.inject.Inject;
2729
import java.time.Clock;
2830
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
2931
import org.apache.polaris.core.PolarisDiagnostics;
@@ -36,6 +38,8 @@
3638

3739
public class QuarkusProducers {
3840

41+
@Inject Instance<DataSource> dataSource;
42+
3943
@Produces
4044
public MetaStoreManagerFactory metaStoreManagerFactory(
4145
@ConfigProperty(name = "polaris.persistence.type") String persistenceType,
@@ -76,4 +80,9 @@ public PolarisConfigurationStore configurationStore() {
7680
// A configuration store is not required when running the admin tool.
7781
return new PolarisConfigurationStore() {};
7882
}
83+
84+
@Produces
85+
public DataSource dataSource() {
86+
return dataSource.get();
87+
}
7988
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.admintool.relational.jdbc;
20+
21+
import io.quarkus.test.junit.TestProfile;
22+
import org.apache.polaris.admintool.BootstrapCommandTestBase;
23+
24+
@TestProfile(RelationalJdbcProfile.class)
25+
public class RelationalJdbcBootstrapCommandTest extends BootstrapCommandTestBase {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.admintool.relational.jdbc;
20+
21+
import static org.apache.polaris.admintool.PostgresTestResourceLifecycleManager.INIT_SCRIPT;
22+
23+
import io.quarkus.test.junit.QuarkusTestProfile;
24+
import java.util.List;
25+
import java.util.Map;
26+
import org.apache.polaris.admintool.PostgresRelationalJdbcLifeCycleManagement;
27+
28+
public class RelationalJdbcProfile implements QuarkusTestProfile {
29+
@Override
30+
public Map<String, String> getConfigOverrides() {
31+
return Map.of();
32+
}
33+
34+
@Override
35+
public List<TestResourceEntry> testResources() {
36+
return List.of(
37+
new TestResourceEntry(
38+
PostgresRelationalJdbcLifeCycleManagement.class,
39+
Map.of(INIT_SCRIPT, "org/apache/polaris/admintool/relational-jdbc/init.sql")));
40+
}
41+
}

0 commit comments

Comments
 (0)