Skip to content

Commit 0abf52d

Browse files
committed
[#1546] Differentiate between PostgreSQL and CockroachDB
Hibernate ORM needs an extra query to differentiate between PostgreSQL and CockroachDB. With a custom Dialect and DialectResolver, we can run the query beforehand and skip that step in Hibernate ORM (Hibernate ORM expectis a JDBC connection).
1 parent 68f520f commit 0abf52d

File tree

7 files changed

+127
-25
lines changed

7 files changed

+127
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.dialect.internal;
7+
8+
import org.hibernate.dialect.CockroachDialect;
9+
10+
/**
11+
* The same as {@link CockroachDialect} in Hibernate ORM, but it doesn't require extra
12+
* queries to parse the version.
13+
*/
14+
public class ReactiveCockroachDialect extends CockroachDialect {
15+
16+
public ReactiveCockroachDialect(String fullVersion) {
17+
super( parseVersion( fullVersion ) );
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.engine.jdbc.dialect.internal;
7+
8+
import org.hibernate.dialect.Database;
9+
import org.hibernate.dialect.Dialect;
10+
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
11+
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
12+
import org.hibernate.reactive.dialect.internal.ReactiveCockroachDialect;
13+
14+
public class ReactiveStandardDialectResolver implements DialectResolver {
15+
16+
@Override
17+
public Dialect resolveDialect(DialectResolutionInfo info) {
18+
// Hibernate ORM runs an extra query to recognize CockroachDB from PostgreSQL
19+
// We've already done it, so we are trying to skip that step
20+
if ( info.getDatabaseName().startsWith( "Cockroach" ) ) {
21+
return new ReactiveCockroachDialect( info.getDatabaseVersion() );
22+
}
23+
24+
for ( Database database : Database.values() ) {
25+
if ( database.matchesResolutionInfo( info ) ) {
26+
return database.createDialect( info );
27+
}
28+
}
29+
30+
return null;
31+
}
32+
}

hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.hibernate.boot.model.TypeContributions;
2020
import org.hibernate.boot.model.TypeContributor;
21+
import org.hibernate.dialect.CockroachDialect;
2122
import org.hibernate.dialect.DB2Dialect;
2223
import org.hibernate.dialect.Dialect;
2324
import org.hibernate.dialect.MySQLDialect;
@@ -272,7 +273,7 @@ private static class ReactiveClobJdbcType implements JdbcType {
272273
private final int defaultSqlTypeCode;
273274

274275
public ReactiveClobJdbcType(Dialect dialect) {
275-
defaultSqlTypeCode = dialect instanceof PostgreSQLDialect
276+
defaultSqlTypeCode = dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect
276277
? Types.LONGVARCHAR
277278
: Types.CLOB;
278279
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.Map;
1010

1111
import org.hibernate.boot.registry.StandardServiceInitiator;
12+
import org.hibernate.dialect.CockroachDialect;
1213
import org.hibernate.dialect.Dialect;
1314
import org.hibernate.dialect.DialectDelegateWrapper;
1415
import org.hibernate.dialect.PostgreSQLDialect;
@@ -52,7 +53,7 @@ public ParameterMarkerStrategy initiateService(Map<String, Object> configuration
5253
* @return the selected strategy for the dialect, never null
5354
*/
5455
private ParameterMarkerStrategy recommendRendered(Dialect realDialect) {
55-
if ( realDialect instanceof PostgreSQLDialect ) {
56+
if ( realDialect instanceof PostgreSQLDialect || realDialect instanceof CockroachDialect ) {
5657
return new PostgreSQLNativeParameterMarkers();
5758
}
5859
if ( realDialect instanceof SQLServerDialect ) {

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

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,18 @@
2828
import io.vertx.sqlclient.spi.DatabaseMetadata;
2929

3030
import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
31+
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
3132
import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture;
3233

3334
/**
3435
* A Hibernate {@link StandardServiceInitiator service initiator} that
3536
* provides an implementation of {@link JdbcEnvironment} that infers
3637
* the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL.
3738
*/
38-
public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEnvironment> {
39+
public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator<JdbcEnvironment> {
3940

4041
/**
41-
* I'm using the same logger category used in {@link org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl}
42+
* I'm using the same logger category used in {@link org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl}.
4243
*/
4344
private static final Log LOG = make( Log.class, new LogCategory( "SQL dialect" ) );
4445

@@ -74,8 +75,7 @@ public DialectBuilder(Map<String, Object> configurationValues, ServiceRegistry r
7475
public Dialect build() {
7576
DialectFactory dialectFactory = registry.getService( DialectFactory.class );
7677
Dialect dialect = dialectFactory.buildDialect( configurationValues, this::dialectResolutionInfo );
77-
dialect = checkDialect( dialect );
78-
return dialect;
78+
return checkDialect( dialect );
7979
}
8080

8181
/**
@@ -102,8 +102,9 @@ private DialectResolutionInfo dialectResolutionInfo() {
102102

103103
private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInfo(ReactiveConnection connection) {
104104
try {
105-
ReactiveDialectResolutionInfo info = new ReactiveDialectResolutionInfo( connection.getDatabaseMetadata() );
106-
return connection.close().thenApply( v -> info );
105+
final DatabaseMetadata databaseMetadata = connection.getDatabaseMetadata();
106+
return resolutionInfoStage( connection, databaseMetadata )
107+
.thenCompose( info -> connection.close().thenApply( v -> info ) );
107108
}
108109
catch (Throwable t) {
109110
try {
@@ -117,6 +118,60 @@ private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInf
117118
}
118119
}
119120
}
121+
122+
private static CompletionStage<ReactiveDialectResolutionInfo> resolutionInfoStage(ReactiveConnection connection, DatabaseMetadata databaseMetadata) {
123+
if ( databaseMetadata.productName().equalsIgnoreCase( "PostgreSQL" ) ) {
124+
// We need to check if the database is PostgreSQL or CockroachDB
125+
// Hibernate ORM does it using a query, so we need to check in advance
126+
// See org.hibernate.dialect.Database.POSTGRESQL#createDialect
127+
return connection.select( "select version()" )
128+
.thenApply( DialectBuilder::readFullVersion )
129+
.thenApply( fullversion -> {
130+
if ( fullversion.startsWith( "Cockroach" ) ) {
131+
return new CockroachDatabaseMetadata( fullversion );
132+
}
133+
return databaseMetadata;
134+
} )
135+
.thenApply( ReactiveDialectResolutionInfo::new );
136+
}
137+
138+
return completedFuture( new ReactiveDialectResolutionInfo( databaseMetadata ) );
139+
}
140+
141+
private static String readFullVersion(ReactiveConnection.Result result) {
142+
return result.hasNext()
143+
? (String) result.next()[0]
144+
: "";
145+
}
146+
}
147+
148+
private static class CockroachDatabaseMetadata implements DatabaseMetadata {
149+
150+
private final String fullversion;
151+
152+
public CockroachDatabaseMetadata(String fullversion) {
153+
this.fullversion = fullversion;
154+
}
155+
156+
@Override
157+
public String productName() {
158+
return "CockroachDb";
159+
}
160+
161+
@Override
162+
public String fullVersion() {
163+
return fullversion;
164+
}
165+
166+
@Override
167+
public int majorVersion() {
168+
return 0;
169+
}
170+
171+
@Override
172+
public int minorVersion() {
173+
return 0;
174+
}
120175
}
121176

122177
private static class ReactiveDialectResolutionInfo implements DialectResolutionInfo {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.hibernate.reactive.engine.jdbc.dialect.internal.ReactiveStandardDialectResolver

hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchedAssociationTest.java

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,25 @@
88
import java.util.ArrayList;
99
import java.util.Collection;
1010
import java.util.List;
11-
import jakarta.persistence.CascadeType;
12-
import jakarta.persistence.Entity;
13-
import jakarta.persistence.FetchType;
14-
import jakarta.persistence.GeneratedValue;
15-
import jakarta.persistence.Id;
16-
import jakarta.persistence.JoinColumn;
17-
import jakarta.persistence.ManyToOne;
18-
import jakarta.persistence.OneToMany;
19-
import jakarta.persistence.Table;
2011

2112
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
2213
import org.hibernate.cfg.Configuration;
2314
import org.hibernate.reactive.testing.DatabaseSelectionRule;
2415
import org.hibernate.reactive.testing.SqlStatementTracker;
2516

26-
import org.junit.After;
2717
import org.junit.Rule;
2818
import org.junit.Test;
2919

3020
import io.vertx.ext.unit.TestContext;
21+
import jakarta.persistence.CascadeType;
22+
import jakarta.persistence.Entity;
23+
import jakarta.persistence.FetchType;
24+
import jakarta.persistence.GeneratedValue;
25+
import jakarta.persistence.Id;
26+
import jakarta.persistence.JoinColumn;
27+
import jakarta.persistence.ManyToOne;
28+
import jakarta.persistence.OneToMany;
29+
import jakarta.persistence.Table;
3130

3231
import static org.assertj.core.api.Assertions.assertThat;
3332
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL;
@@ -54,13 +53,6 @@ protected Configuration constructConfiguration() {
5453
return configuration;
5554
}
5655

57-
@After
58-
public void cleanDb(TestContext context) {
59-
test( context, getSessionFactory()
60-
.withTransaction( s -> s.createQuery( "delete from Child" ).executeUpdate()
61-
.thenCompose( v -> s.createQuery( "delete from Parent" ).executeUpdate() ) ) );
62-
}
63-
6456
private static boolean isSelectOrInsertQuery(String s) {
6557
return s.toLowerCase().startsWith( "select" )
6658
|| s.toLowerCase().startsWith( "insert" );
@@ -100,6 +92,7 @@ public void testWithMutiny(TestContext context) {
10092
// We don't expect a select from CHILD
10193
private String[] getExpectedNativeQueries() {
10294
return new String[] {
95+
"select version()",
10396
"select nextval('PARENT_SEQ')",
10497
"insert into PARENT (name,id) values ($1,$2)",
10598
"select p1_0.id,p1_0.name from PARENT p1_0",

0 commit comments

Comments
 (0)