55 */
66package org .hibernate .reactive .provider .service ;
77
8- import java .util .Map ;
9- import java .util .concurrent .CompletionStage ;
10-
118import org .hibernate .boot .registry .StandardServiceInitiator ;
129import org .hibernate .dialect .Dialect ;
10+ import org .hibernate .engine .jdbc .connections .spi .DatabaseConnectionInfo ;
1311import org .hibernate .engine .jdbc .dialect .spi .DialectFactory ;
1412import org .hibernate .engine .jdbc .dialect .spi .DialectResolutionInfo ;
1513import org .hibernate .engine .jdbc .env .internal .JdbcEnvironmentImpl ;
14+ import org .hibernate .engine .jdbc .env .internal .JdbcEnvironmentInitiator ;
1615import org .hibernate .engine .jdbc .env .spi .JdbcEnvironment ;
1716import org .hibernate .engine .jdbc .spi .SqlExceptionHelper ;
1817import org .hibernate .reactive .pool .ReactiveConnection ;
1918import org .hibernate .reactive .pool .ReactiveConnectionPool ;
20- import org .hibernate .reactive .provider .Settings ;
2119import org .hibernate .reactive .util .impl .CompletionStages ;
2220import org .hibernate .service .ServiceRegistry ;
2321import org .hibernate .service .spi .ServiceRegistryImplementor ;
2422
2523import 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 ;
2731import static java .util .function .Function .identity ;
2832import static org .hibernate .reactive .util .impl .CompletionStages .completedFuture ;
2933
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