Skip to content

Commit 56c9d1d

Browse files
committed
[JDBC] Part2: Add JDBC module
1 parent 7018f65 commit 56c9d1d

File tree

21 files changed

+2375
-38
lines changed

21 files changed

+2375
-38
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Apache Polaris is organized into the following modules:
6161
- Persistence modules:
6262
- `polaris-jpa-model` - The JPA entity definitions
6363
- `polaris-eclipselink` - The Eclipselink implementation of the MetaStoreManager interface
64+
- `polaris-relational-jdbc` - The JDBC implementation of BasePersistence to be used via AtomicMetaStoreManager
6465

6566
Apache Polaris is built using Gradle with Java 21+ and Docker 27+.
6667

bom/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
api(project(":polaris-service-common"))
4343

4444
api(project(":polaris-eclipselink"))
45+
api(project(":polaris-relational-jdbc"))
4546
api(project(":polaris-jpa-model"))
4647

4748
api(project(":polaris-quarkus-admin"))

extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/EclipseLinkPolarisMetaStoreManagerFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import java.nio.file.Path;
2727
import org.apache.polaris.core.PolarisDiagnostics;
2828
import org.apache.polaris.core.context.RealmContext;
29-
import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory;
29+
import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerBackedByStoreFactory;
3030
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
3131
import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
3232
import org.apache.polaris.core.persistence.transactional.TransactionalPersistence;
@@ -40,7 +40,7 @@
4040
@ApplicationScoped
4141
@Identifier("eclipse-link")
4242
public class EclipseLinkPolarisMetaStoreManagerFactory
43-
extends LocalPolarisMetaStoreManagerFactory<PolarisEclipseLinkStore> {
43+
extends LocalPolarisMetaStoreManagerBackedByStoreFactory<PolarisEclipseLinkStore> {
4444

4545
@Inject EclipseLinkConfiguration eclipseLinkConfiguration;
4646
@Inject PolarisStorageIntegrationProvider storageIntegrationProvider;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
20+
plugins { id("polaris-server") }
21+
22+
dependencies {
23+
implementation(project(":polaris-core"))
24+
implementation(libs.slf4j.api)
25+
26+
compileOnly(libs.jakarta.annotation.api)
27+
compileOnly(libs.jakarta.enterprise.cdi.api)
28+
compileOnly(libs.jakarta.inject.api)
29+
30+
implementation(libs.smallrye.common.annotation) // @Identifier
31+
32+
runtimeOnly(libs.postgresql)
33+
testImplementation(libs.mockito.junit.jupiter)
34+
35+
testImplementation(libs.h2)
36+
testImplementation(testFixtures(project(":polaris-core")))
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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.extension.persistence.relational.jdbc;
20+
21+
import static java.nio.charset.StandardCharsets.UTF_8;
22+
23+
import java.io.BufferedReader;
24+
import java.io.IOException;
25+
import java.io.InputStreamReader;
26+
import java.sql.Connection;
27+
import java.sql.ResultSet;
28+
import java.sql.SQLException;
29+
import java.sql.Statement;
30+
import java.util.List;
31+
import java.util.Objects;
32+
import javax.sql.DataSource;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
36+
public class DatasourceOperations {
37+
private static final Logger LOGGER = LoggerFactory.getLogger(DatasourceOperations.class);
38+
39+
private static final String ALREADY_EXISTS_STATE_POSTGRES = "42P07";
40+
private static final String CONSTRAINT_VIOLATION_SQL_CODE = "23505";
41+
42+
private final DataSource datasource;
43+
44+
public DatasourceOperations(DataSource datasource) {
45+
this.datasource = datasource;
46+
}
47+
48+
public void executeScript(String scriptFilePath) throws SQLException {
49+
ClassLoader classLoader = DatasourceOperations.class.getClassLoader();
50+
try (Connection connection = borrowConnection();
51+
Statement statement = connection.createStatement()) {
52+
BufferedReader reader =
53+
new BufferedReader(
54+
new InputStreamReader(
55+
Objects.requireNonNull(classLoader.getResourceAsStream(scriptFilePath)), UTF_8));
56+
StringBuilder sqlBuffer = new StringBuilder();
57+
String line;
58+
while ((line = reader.readLine()) != null) {
59+
line = line.trim();
60+
if (!line.isEmpty() && !line.startsWith("--")) { // Ignore empty lines and comments
61+
sqlBuffer.append(line).append("\n");
62+
if (line.endsWith(";")) { // Execute statement when semicolon is found
63+
String sql = sqlBuffer.toString().trim();
64+
try {
65+
int rowsUpdated = statement.executeUpdate(sql);
66+
LOGGER.debug("Query {} executed {} rows affected", sql, rowsUpdated);
67+
} catch (SQLException e) {
68+
LOGGER.error("Error executing query {}", sql, e);
69+
// re:throw this as unhandled exception
70+
throw new RuntimeException(e);
71+
}
72+
sqlBuffer.setLength(0); // Clear the buffer for the next statement
73+
}
74+
}
75+
}
76+
} catch (IOException e) {
77+
LOGGER.error("Error reading the script file", e);
78+
throw new RuntimeException(e);
79+
} catch (SQLException e) {
80+
LOGGER.error("Error executing the script file", e);
81+
throw e;
82+
}
83+
}
84+
85+
public <T> List<T> executeSelect(String query, Class<T> targetClass) throws SQLException {
86+
try (Connection connection = borrowConnection();
87+
Statement statement = connection.createStatement();
88+
ResultSet s = statement.executeQuery(query)) {
89+
List<T> results = ResultSetToObjectConverter.convert(s, targetClass);
90+
return results.isEmpty() ? null : results;
91+
} catch (SQLException e) {
92+
LOGGER.error("Error executing query {}", query, e);
93+
throw e;
94+
} catch (Exception e) {
95+
throw new RuntimeException(e);
96+
}
97+
}
98+
99+
public int executeUpdate(String query) throws SQLException {
100+
try (Connection connection = borrowConnection();
101+
Statement statement = connection.createStatement()) {
102+
return statement.executeUpdate(query);
103+
} catch (SQLException e) {
104+
LOGGER.error("Error executing query {}", query, e);
105+
throw e;
106+
}
107+
}
108+
109+
public int executeUpdate(String query, Statement statement) throws SQLException {
110+
LOGGER.debug("Executing query {} within transaction", query);
111+
try {
112+
return statement.executeUpdate(query);
113+
} catch (SQLException e) {
114+
LOGGER.error("Error executing query {}", query, e);
115+
throw e;
116+
}
117+
}
118+
119+
public void runWithinTransaction(TransactionCallback callback) throws SQLException {
120+
Connection connection = null;
121+
try {
122+
connection = borrowConnection();
123+
connection.setAutoCommit(false); // Disable auto-commit to start a transaction
124+
125+
boolean result;
126+
try (Statement statement = connection.createStatement()) {
127+
result = callback.execute(statement);
128+
}
129+
130+
if (result) {
131+
connection.commit(); // Commit the transaction if successful
132+
} else {
133+
connection.rollback(); // Rollback the transaction if not successful
134+
}
135+
136+
} catch (SQLException e) {
137+
if (connection != null) {
138+
try {
139+
connection.rollback(); // Rollback on exception
140+
} catch (SQLException ex) {
141+
LOGGER.error("Error rolling back transaction", ex);
142+
throw e;
143+
}
144+
}
145+
LOGGER.error("Caught Error while executing transaction", e);
146+
throw e;
147+
} finally {
148+
if (connection != null) {
149+
try {
150+
connection.setAutoCommit(true); // Restore auto-commit
151+
connection.close();
152+
} catch (SQLException e) {
153+
LOGGER.error("Error closing connection", e);
154+
throw e;
155+
}
156+
}
157+
}
158+
}
159+
160+
// Interface for transaction callback
161+
public interface TransactionCallback {
162+
boolean execute(Statement statement) throws SQLException;
163+
}
164+
165+
public boolean isConstraintViolation(SQLException e) {
166+
return CONSTRAINT_VIOLATION_SQL_CODE.equals(e.getSQLState());
167+
}
168+
169+
public boolean isAlreadyExistsException(SQLException e) {
170+
return ALREADY_EXISTS_STATE_POSTGRES.equals(e.getSQLState());
171+
}
172+
173+
private Connection borrowConnection() throws SQLException {
174+
return datasource.getConnection();
175+
}
176+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.extension.persistence.relational.jdbc;
20+
21+
import java.nio.ByteBuffer;
22+
import java.security.SecureRandom;
23+
import java.util.UUID;
24+
25+
public class IdGenerator {
26+
private IdGenerator() {}
27+
28+
public static final IdGenerator idGenerator = new IdGenerator();
29+
30+
private static final long LONG_MAX_ID = 0x7fffffffffffffffL;
31+
32+
public long nextId() {
33+
// Make sure this is a positive number.
34+
// conflicting ids don't get accepted and is enforced by table constraints.
35+
return generateSecureRandomUUID().getLeastSignificantBits() & LONG_MAX_ID;
36+
}
37+
38+
private UUID generateSecureRandomUUID() {
39+
SecureRandom secureRandom = new SecureRandom();
40+
byte[] randomBytes = new byte[16];
41+
secureRandom.nextBytes(randomBytes);
42+
43+
// Ensure the most significant bits of the time_hi_and_version field are 0001
44+
randomBytes[6] &= 0x0f; // Clear version bits
45+
randomBytes[6] |= 0x40; // Set version to 4 (random)
46+
47+
// Ensure the most significant bits of the clock_seq_hi_and_reserved field are 10
48+
randomBytes[8] &= 0x3f; // Clear variant bits
49+
randomBytes[8] |= 0x80; // Set variant to RFC 4122
50+
51+
ByteBuffer bb = ByteBuffer.wrap(randomBytes);
52+
long mostSigBits = bb.getLong();
53+
long leastSigBits = bb.getLong();
54+
55+
return new UUID(mostSigBits, leastSigBits);
56+
}
57+
}

0 commit comments

Comments
 (0)