Skip to content

Commit 02aed1b

Browse files
committed
[#2129] Allow offline startup and on-demand DB version checks
This feature is useful in particular for applications that start up before the DB becomes accessible. It can also be useful for Quarkus, where (part of) startup happens at build time. This achieved by setting the property: ``` hibernate.boot.allow_jdbc_metadata_access = false ```
1 parent 9a129c2 commit 02aed1b

File tree

2 files changed

+167
-43
lines changed

2 files changed

+167
-43
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/dialect/internal/ReactiveStandardDialectResolver.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,22 @@
1111
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
1212
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
1313

14-
import static org.hibernate.dialect.CockroachDialect.parseVersion;
1514

1615
public class ReactiveStandardDialectResolver implements DialectResolver {
1716

1817
@Override
1918
public Dialect resolveDialect(DialectResolutionInfo info) {
20-
// Hibernate ORM runs an extra query to recognize CockroachDB from PostgreSQL
21-
// We've already done it, so we are trying to skip that step
19+
// Hibernate ORM runs an extra query to recognize CockroachDB from PostgresSQL
20+
// We already did it when we created the DialectResolutionInfo in NoJdbcEnvironmentInitiator,
21+
// so we can skip that step here.
2222
if ( info.getDatabaseName().startsWith( "Cockroach" ) ) {
23-
return new CockroachDialect( parseVersion( info.getDatabaseVersion() ) );
23+
return new CockroachDialect( info );
2424
}
25-
2625
for ( Database database : Database.values() ) {
2726
if ( database.matchesResolutionInfo( info ) ) {
2827
return database.createDialect( info );
2928
}
3029
}
31-
3230
return null;
3331
}
3432
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java

Lines changed: 163 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,29 @@
55
*/
66
package org.hibernate.reactive.provider.service;
77

8-
import java.util.Map;
9-
import java.util.concurrent.CompletionStage;
10-
118
import org.hibernate.boot.registry.StandardServiceInitiator;
129
import org.hibernate.dialect.Dialect;
10+
import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo;
1311
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
1412
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
1513
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl;
14+
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
1615
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
1716
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
1817
import org.hibernate.reactive.pool.ReactiveConnection;
1918
import org.hibernate.reactive.pool.ReactiveConnectionPool;
20-
import org.hibernate.reactive.provider.Settings;
2119
import org.hibernate.reactive.util.impl.CompletionStages;
2220
import org.hibernate.service.ServiceRegistry;
2321
import org.hibernate.service.spi.ServiceRegistryImplementor;
2422

2523
import io.vertx.sqlclient.spi.DatabaseMetadata;
24+
import java.util.Map;
25+
import java.util.StringTokenizer;
26+
import java.util.concurrent.CompletionStage;
27+
import java.util.function.Function;
2628

29+
import static java.lang.Integer.parseInt;
30+
import static java.util.Objects.requireNonNullElse;
2731
import static java.util.function.Function.identity;
2832
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
2933

@@ -32,7 +36,8 @@
3236
* that provides an implementation of {@link JdbcEnvironment} that infers
3337
* the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL.
3438
*/
35-
public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEnvironment> {
39+
public class NoJdbcEnvironmentInitiator extends JdbcEnvironmentInitiator
40+
implements StandardServiceInitiator<JdbcEnvironment> {
3641

3742
public static final NoJdbcEnvironmentInitiator INSTANCE = new NoJdbcEnvironmentInitiator();
3843

@@ -42,14 +47,59 @@ public Class<JdbcEnvironment> getServiceInitiated() {
4247
}
4348

4449
@Override
45-
public JdbcEnvironment initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
46-
boolean explicitDialect = configurationValues.containsKey( Settings.DIALECT );
47-
if ( explicitDialect ) {
48-
DialectFactory dialectFactory = registry.getService( DialectFactory.class );
49-
return new JdbcEnvironmentImpl( registry, dialectFactory.buildDialect( configurationValues, null ) );
50-
}
50+
protected void logConnectionInfo(DatabaseConnectionInfo databaseConnectionInfo) {
51+
// Nothing to do we log the connection info somewhere else
52+
}
53+
54+
@Override
55+
protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration(
56+
Map<String, Object> configurationValues,
57+
ServiceRegistryImplementor registry,
58+
DialectFactory dialectFactory,
59+
DialectResolutionInfo dialectResolutionInfo) {
60+
return super.getJdbcEnvironmentWithExplicitConfiguration(
61+
configurationValues,
62+
registry,
63+
dialectFactory,
64+
dialectResolutionInfo
65+
);
66+
}
5167

52-
return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry ).build() );
68+
@Override
69+
protected JdbcEnvironmentImpl getJdbcEnvironmentWithDefaults(
70+
Map<String, Object> configurationValues,
71+
ServiceRegistryImplementor registry,
72+
DialectFactory dialectFactory) {
73+
return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry )
74+
.build( dialectFactory )
75+
);
76+
}
77+
78+
@Override
79+
protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
80+
Map<String, Object> configurationValues,
81+
ServiceRegistryImplementor registry,
82+
DialectFactory dialectFactory,
83+
String explicitDatabaseName,
84+
Integer explicitDatabaseMajorVersion,
85+
Integer explicitDatabaseMinorVersion,
86+
String explicitDatabaseVersion) {
87+
try {
88+
final Dialect dialect = new DialectBuilder( configurationValues, registry )
89+
.build(
90+
dialectFactory,
91+
new ExplicitMetadata(
92+
explicitDatabaseName,
93+
explicitDatabaseMajorVersion,
94+
explicitDatabaseMinorVersion,
95+
explicitDatabaseVersion
96+
)
97+
);
98+
return new JdbcEnvironmentImpl( registry, dialect );
99+
}
100+
catch (RuntimeException e) {
101+
return getJdbcEnvironmentWithDefaults( configurationValues, registry, dialectFactory );
102+
}
53103
}
54104

55105
private static class DialectBuilder {
@@ -62,24 +112,40 @@ public DialectBuilder(Map<String, Object> configurationValues, ServiceRegistry r
62112
this.registry = registry;
63113
}
64114

65-
public Dialect build() {
66-
DialectFactory dialectFactory = registry.getService( DialectFactory.class );
115+
public Dialect build(DialectFactory dialectFactory) {
67116
return dialectFactory.buildDialect( configurationValues, this::dialectResolutionInfo );
68117
}
69118

119+
public Dialect build(DialectFactory dialectFactory, ExplicitMetadata explicitMetadata) {
120+
return dialectFactory.buildDialect( configurationValues, () -> dialectResolutionInfo( explicitMetadata ) );
121+
}
122+
70123
private DialectResolutionInfo dialectResolutionInfo() {
71-
ReactiveConnectionPool connectionPool = registry.getService( ReactiveConnectionPool.class );
72-
return connectionPool
124+
return dialectResolutionInfo( DialectBuilder::buildResolutionInfo );
125+
}
126+
127+
private DialectResolutionInfo dialectResolutionInfo(ExplicitMetadata explicitMetadata) {
128+
return dialectResolutionInfo( reactiveConnection -> DialectBuilder
129+
.buildResolutionInfo( reactiveConnection, explicitMetadata )
130+
);
131+
}
132+
133+
private DialectResolutionInfo dialectResolutionInfo(Function<ReactiveConnection, CompletionStage<ReactiveDialectResolutionInfo>> dialectResolutionFunction) {
134+
return registry
135+
.getService( ReactiveConnectionPool.class )
73136
// The default SqlExceptionHelper in ORM requires the dialect, but we haven't created a dialect yet,
74137
// so we need to override it at this stage, or we will have an exception.
75138
.getConnection( new SqlExceptionHelper( true ) )
76-
.thenCompose( DialectBuilder::buildResolutionInfo )
139+
.thenCompose( dialectResolutionFunction )
77140
.toCompletableFuture().join();
78141
}
79142

80143
private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInfo(ReactiveConnection connection) {
81-
final DatabaseMetadata databaseMetadata = connection.getDatabaseMetadata();
82-
return resolutionInfoStage( connection, databaseMetadata )
144+
return buildResolutionInfo( connection, null );
145+
}
146+
147+
private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInfo(ReactiveConnection connection, ExplicitMetadata explicitMetadata) {
148+
return resolutionInfoStage( connection, explicitMetadata )
83149
.handle( CompletionStages::handle )
84150
.thenCompose( handled -> {
85151
if ( handled.hasFailed() ) {
@@ -96,19 +162,27 @@ private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInf
96162
} );
97163
}
98164

99-
private static CompletionStage<ReactiveDialectResolutionInfo> resolutionInfoStage(ReactiveConnection connection, DatabaseMetadata databaseMetadata) {
100-
if ( databaseMetadata.productName().equalsIgnoreCase( "PostgreSQL" ) ) {
101-
// We need to check if the database is PostgreSQL or CockroachDB
102-
// Hibernate ORM does it using a query, so we need to check in advance
165+
/**
166+
* @see org.hibernate.dialect.Database#POSTGRESQL for recognizing CockroachDB
167+
*/
168+
private static CompletionStage<ReactiveDialectResolutionInfo> resolutionInfoStage(ReactiveConnection connection, ExplicitMetadata explicitMetadata) {
169+
final DatabaseMetadata databaseMetadata = explicitMetadata != null
170+
? new ReactiveDatabaseMetadata( connection.getDatabaseMetadata(), explicitMetadata )
171+
: connection.getDatabaseMetadata();
172+
173+
// If the product name is explicitly set to Postgres, we are not going to override it
174+
if ( ( explicitMetadata == null || explicitMetadata.productName == null )
175+
&& databaseMetadata.productName().equalsIgnoreCase( "PostgreSQL" ) ) {
176+
// CockroachDB returns "PostgreSQL" as product name in the metadata.
177+
// So, we need to check if the database is PostgreSQL or CockroachDB
178+
// We follow the same approach used by ORM: run a new query and check the full version metadata
103179
// See org.hibernate.dialect.Database.POSTGRESQL#createDialect
104180
return connection.select( "select version()" )
105181
.thenApply( DialectBuilder::readFullVersion )
106-
.thenApply( fullversion -> {
107-
if ( fullversion.startsWith( "Cockroach" ) ) {
108-
return new CockroachDatabaseMetadata( fullversion );
109-
}
110-
return databaseMetadata;
111-
} )
182+
.thenApply( fullVersion -> fullVersion.startsWith( "Cockroach" )
183+
? new ReactiveDatabaseMetadata( "Cockroach", databaseMetadata )
184+
: databaseMetadata
185+
)
112186
.thenApply( ReactiveDialectResolutionInfo::new );
113187
}
114188

@@ -122,32 +196,62 @@ private static String readFullVersion(ReactiveConnection.Result result) {
122196
}
123197
}
124198

125-
private static class CockroachDatabaseMetadata implements DatabaseMetadata {
199+
/**
200+
* Utility class to pass around explicit metadata properties.
201+
* It's different from {@link DatabaseMetadata} because values can be null.
202+
*/
203+
private static class ExplicitMetadata {
204+
private final String productName;
205+
private final String fullVersion;
206+
private final Integer majorVersion;
207+
private final Integer minorVersion;
208+
209+
public ExplicitMetadata(String explicitDatabaseName, Integer explicitDatabaseMajorVersion, Integer explicitDatabaseMinorVersion, String explicitDatabaseVersion ) {
210+
this.productName = explicitDatabaseName;
211+
this.fullVersion = explicitDatabaseVersion;
212+
this.majorVersion = explicitDatabaseMajorVersion;
213+
this.minorVersion = explicitDatabaseMinorVersion;
214+
}
215+
}
126216

127-
private final String fullversion;
217+
private static class ReactiveDatabaseMetadata implements DatabaseMetadata {
218+
public final String productName;
219+
public final String fullVersion;
220+
public final int majorVersion;
221+
public final int minorVersion;
222+
223+
public ReactiveDatabaseMetadata(String productName, DatabaseMetadata databaseMetadata) {
224+
this.productName = productName;
225+
this.fullVersion = databaseMetadata.productName();
226+
this.majorVersion = databaseMetadata.majorVersion();
227+
this.minorVersion = databaseMetadata.minorVersion();
228+
}
128229

129-
public CockroachDatabaseMetadata(String fullversion) {
130-
this.fullversion = fullversion;
230+
public ReactiveDatabaseMetadata(DatabaseMetadata metadata, ExplicitMetadata explicitMetadata) {
231+
productName = requireNonNullElse( explicitMetadata.productName, metadata.productName() );
232+
fullVersion = requireNonNullElse( explicitMetadata.fullVersion, metadata.fullVersion() );
233+
majorVersion = requireNonNullElse( explicitMetadata.majorVersion, metadata.majorVersion() );
234+
minorVersion = requireNonNullElse( explicitMetadata.minorVersion, metadata.minorVersion() );
131235
}
132236

133237
@Override
134238
public String productName() {
135-
return "CockroachDb";
239+
return productName;
136240
}
137241

138242
@Override
139243
public String fullVersion() {
140-
return fullversion;
244+
return fullVersion;
141245
}
142246

143247
@Override
144248
public int majorVersion() {
145-
return 0;
249+
return majorVersion;
146250
}
147251

148252
@Override
149253
public int minorVersion() {
150-
return 0;
254+
return minorVersion;
151255
}
152256
}
153257

@@ -179,6 +283,27 @@ public int getDatabaseMinorVersion() {
179283
return metadata.minorVersion();
180284
}
181285

286+
@Override
287+
public int getDatabaseMicroVersion() {
288+
return databaseMicroVersion( metadata.fullVersion(), metadata.majorVersion(), metadata.minorVersion() );
289+
}
290+
291+
// We should move this in ORM and avoid duplicated code
292+
private static int databaseMicroVersion(String version, int major, int minor) {
293+
final String prefix = major + "." + minor + ".";
294+
if ( version.startsWith( prefix ) ) {
295+
try {
296+
final String substring = version.substring( prefix.length() );
297+
final String micro = new StringTokenizer( substring, " .,-:;/()[]" ).nextToken();
298+
return parseInt( micro );
299+
}
300+
catch (NumberFormatException nfe) {
301+
return 0;
302+
}
303+
}
304+
return 0;
305+
}
306+
182307
@Override
183308
public String getDriverName() {
184309
return getDatabaseName();
@@ -196,6 +321,7 @@ public int getDriverMinorVersion() {
196321

197322
@Override
198323
public String getSQLKeywords() {
324+
// Vert.x metadata doesn't have this info
199325
return null;
200326
}
201327

0 commit comments

Comments
 (0)