Skip to content

Commit abc7715

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 b79ae76 commit abc7715

File tree

2 files changed

+153
-43
lines changed

2 files changed

+153
-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: 149 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,28 @@
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;
9+
import org.hibernate.dialect.DatabaseVersion;
1210
import org.hibernate.dialect.Dialect;
11+
import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo;
1312
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
1413
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
1514
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl;
15+
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
1616
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
1717
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
1818
import org.hibernate.reactive.pool.ReactiveConnection;
1919
import org.hibernate.reactive.pool.ReactiveConnectionPool;
20-
import org.hibernate.reactive.provider.Settings;
2120
import org.hibernate.reactive.util.impl.CompletionStages;
2221
import org.hibernate.service.ServiceRegistry;
2322
import org.hibernate.service.spi.ServiceRegistryImplementor;
2423

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

29+
import static java.util.Objects.requireNonNullElse;
2730
import static java.util.function.Function.identity;
2831
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
2932

@@ -32,7 +35,8 @@
3235
* that provides an implementation of {@link JdbcEnvironment} that infers
3336
* the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL.
3437
*/
35-
public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEnvironment> {
38+
public class NoJdbcEnvironmentInitiator extends JdbcEnvironmentInitiator
39+
implements StandardServiceInitiator<JdbcEnvironment> {
3640

3741
public static final NoJdbcEnvironmentInitiator INSTANCE = new NoJdbcEnvironmentInitiator();
3842

@@ -42,14 +46,59 @@ public Class<JdbcEnvironment> getServiceInitiated() {
4246
}
4347

4448
@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-
}
49+
protected void logConnectionInfo(DatabaseConnectionInfo databaseConnectionInfo) {
50+
// Nothing to do we log the connection info somewhere else
51+
}
5152

52-
return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry ).build() );
53+
@Override
54+
protected JdbcEnvironmentImpl getJdbcEnvironmentWithExplicitConfiguration(
55+
Map<String, Object> configurationValues,
56+
ServiceRegistryImplementor registry,
57+
DialectFactory dialectFactory,
58+
DialectResolutionInfo dialectResolutionInfo) {
59+
return super.getJdbcEnvironmentWithExplicitConfiguration(
60+
configurationValues,
61+
registry,
62+
dialectFactory,
63+
dialectResolutionInfo
64+
);
65+
}
66+
67+
@Override
68+
protected JdbcEnvironmentImpl getJdbcEnvironmentWithDefaults(
69+
Map<String, Object> configurationValues,
70+
ServiceRegistryImplementor registry,
71+
DialectFactory dialectFactory) {
72+
return new JdbcEnvironmentImpl( registry, new DialectBuilder( configurationValues, registry )
73+
.build( dialectFactory )
74+
);
75+
}
76+
77+
@Override
78+
protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata(
79+
Map<String, Object> configurationValues,
80+
ServiceRegistryImplementor registry,
81+
DialectFactory dialectFactory,
82+
String explicitDatabaseName,
83+
Integer explicitDatabaseMajorVersion,
84+
Integer explicitDatabaseMinorVersion,
85+
String explicitDatabaseVersion) {
86+
try {
87+
final Dialect dialect = new DialectBuilder( configurationValues, registry )
88+
.build(
89+
dialectFactory,
90+
new ExplicitMetadata(
91+
explicitDatabaseName,
92+
explicitDatabaseMajorVersion,
93+
explicitDatabaseMinorVersion,
94+
explicitDatabaseVersion
95+
)
96+
);
97+
return new JdbcEnvironmentImpl( registry, dialect );
98+
}
99+
catch (RuntimeException e) {
100+
return getJdbcEnvironmentWithDefaults( configurationValues, registry, dialectFactory );
101+
}
53102
}
54103

55104
private static class DialectBuilder {
@@ -62,24 +111,40 @@ public DialectBuilder(Map<String, Object> configurationValues, ServiceRegistry r
62111
this.registry = registry;
63112
}
64113

65-
public Dialect build() {
66-
DialectFactory dialectFactory = registry.getService( DialectFactory.class );
114+
public Dialect build(DialectFactory dialectFactory) {
67115
return dialectFactory.buildDialect( configurationValues, this::dialectResolutionInfo );
68116
}
69117

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

80142
private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInfo(ReactiveConnection connection) {
81-
final DatabaseMetadata databaseMetadata = connection.getDatabaseMetadata();
82-
return resolutionInfoStage( connection, databaseMetadata )
143+
return buildResolutionInfo( connection, null );
144+
}
145+
146+
private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInfo(ReactiveConnection connection, ExplicitMetadata explicitMetadata) {
147+
return resolutionInfoStage( connection, explicitMetadata )
83148
.handle( CompletionStages::handle )
84149
.thenCompose( handled -> {
85150
if ( handled.hasFailed() ) {
@@ -96,19 +161,27 @@ private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInf
96161
} );
97162
}
98163

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

@@ -122,32 +195,62 @@ private static String readFullVersion(ReactiveConnection.Result result) {
122195
}
123196
}
124197

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

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

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

133236
@Override
134237
public String productName() {
135-
return "CockroachDb";
238+
return productName;
136239
}
137240

138241
@Override
139242
public String fullVersion() {
140-
return fullversion;
243+
return fullVersion;
141244
}
142245

143246
@Override
144247
public int majorVersion() {
145-
return 0;
248+
return majorVersion;
146249
}
147250

148251
@Override
149252
public int minorVersion() {
150-
return 0;
253+
return minorVersion;
151254
}
152255
}
153256

@@ -179,6 +282,14 @@ public int getDatabaseMinorVersion() {
179282
return metadata.minorVersion();
180283
}
181284

285+
@Override
286+
public int getDatabaseMicroVersion() {
287+
// Hibernate ORM extracts the micro version from the product version using
288+
// databaseMicroVersion(DatabaseMetaData) in JdbcEnvironmentInitiator.
289+
// But, I don't think it's used at the moment, so I will just ignore it.
290+
return DatabaseVersion.NO_VERSION;
291+
}
292+
182293
@Override
183294
public String getDriverName() {
184295
return getDatabaseName();
@@ -196,6 +307,7 @@ public int getDriverMinorVersion() {
196307

197308
@Override
198309
public String getSQLKeywords() {
310+
// Vert.x metadata doesn't have this info
199311
return null;
200312
}
201313

0 commit comments

Comments
 (0)