5
5
*/
6
6
package org .hibernate .reactive .provider .service ;
7
7
8
- import java .util .Map ;
9
- import java .util .concurrent .CompletionStage ;
10
-
11
8
import org .hibernate .boot .registry .StandardServiceInitiator ;
12
9
import org .hibernate .dialect .Dialect ;
10
+ import org .hibernate .engine .jdbc .connections .spi .DatabaseConnectionInfo ;
13
11
import org .hibernate .engine .jdbc .dialect .spi .DialectFactory ;
14
12
import org .hibernate .engine .jdbc .dialect .spi .DialectResolutionInfo ;
15
13
import org .hibernate .engine .jdbc .env .internal .JdbcEnvironmentImpl ;
14
+ import org .hibernate .engine .jdbc .env .internal .JdbcEnvironmentInitiator ;
16
15
import org .hibernate .engine .jdbc .env .spi .JdbcEnvironment ;
17
16
import org .hibernate .engine .jdbc .spi .SqlExceptionHelper ;
18
17
import org .hibernate .reactive .pool .ReactiveConnection ;
19
18
import org .hibernate .reactive .pool .ReactiveConnectionPool ;
20
- import org .hibernate .reactive .provider .Settings ;
21
19
import org .hibernate .reactive .util .impl .CompletionStages ;
22
20
import org .hibernate .service .ServiceRegistry ;
23
21
import org .hibernate .service .spi .ServiceRegistryImplementor ;
24
22
25
23
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 ;
26
28
29
+ import static java .lang .Integer .parseInt ;
30
+ import static java .util .Objects .requireNonNullElse ;
27
31
import static java .util .function .Function .identity ;
28
32
import static org .hibernate .reactive .util .impl .CompletionStages .completedFuture ;
29
33
32
36
* that provides an implementation of {@link JdbcEnvironment} that infers
33
37
* the Hibernate {@link org.hibernate.dialect.Dialect} from the JDBC URL.
34
38
*/
35
- public class NoJdbcEnvironmentInitiator implements StandardServiceInitiator <JdbcEnvironment > {
39
+ public class NoJdbcEnvironmentInitiator extends JdbcEnvironmentInitiator
40
+ implements StandardServiceInitiator <JdbcEnvironment > {
36
41
37
42
public static final NoJdbcEnvironmentInitiator INSTANCE = new NoJdbcEnvironmentInitiator ();
38
43
@@ -42,14 +47,59 @@ public Class<JdbcEnvironment> getServiceInitiated() {
42
47
}
43
48
44
49
@ 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
+ }
51
67
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
+ }
53
103
}
54
104
55
105
private static class DialectBuilder {
@@ -62,24 +112,40 @@ public DialectBuilder(Map<String, Object> configurationValues, ServiceRegistry r
62
112
this .registry = registry ;
63
113
}
64
114
65
- public Dialect build () {
66
- DialectFactory dialectFactory = registry .getService ( DialectFactory .class );
115
+ public Dialect build (DialectFactory dialectFactory ) {
67
116
return dialectFactory .buildDialect ( configurationValues , this ::dialectResolutionInfo );
68
117
}
69
118
119
+ public Dialect build (DialectFactory dialectFactory , ExplicitMetadata explicitMetadata ) {
120
+ return dialectFactory .buildDialect ( configurationValues , () -> dialectResolutionInfo ( explicitMetadata ) );
121
+ }
122
+
70
123
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 )
73
136
// The default SqlExceptionHelper in ORM requires the dialect, but we haven't created a dialect yet,
74
137
// so we need to override it at this stage, or we will have an exception.
75
138
.getConnection ( new SqlExceptionHelper ( true ) )
76
- .thenCompose ( DialectBuilder :: buildResolutionInfo )
139
+ .thenCompose ( dialectResolutionFunction )
77
140
.toCompletableFuture ().join ();
78
141
}
79
142
80
143
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 )
83
149
.handle ( CompletionStages ::handle )
84
150
.thenCompose ( handled -> {
85
151
if ( handled .hasFailed () ) {
@@ -96,19 +162,27 @@ private static CompletionStage<ReactiveDialectResolutionInfo> buildResolutionInf
96
162
} );
97
163
}
98
164
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
103
179
// See org.hibernate.dialect.Database.POSTGRESQL#createDialect
104
180
return connection .select ( "select version()" )
105
181
.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
+ )
112
186
.thenApply ( ReactiveDialectResolutionInfo ::new );
113
187
}
114
188
@@ -122,32 +196,62 @@ private static String readFullVersion(ReactiveConnection.Result result) {
122
196
}
123
197
}
124
198
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
+ }
126
216
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
+ }
128
229
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 () );
131
235
}
132
236
133
237
@ Override
134
238
public String productName () {
135
- return "CockroachDb" ;
239
+ return productName ;
136
240
}
137
241
138
242
@ Override
139
243
public String fullVersion () {
140
- return fullversion ;
244
+ return fullVersion ;
141
245
}
142
246
143
247
@ Override
144
248
public int majorVersion () {
145
- return 0 ;
249
+ return majorVersion ;
146
250
}
147
251
148
252
@ Override
149
253
public int minorVersion () {
150
- return 0 ;
254
+ return minorVersion ;
151
255
}
152
256
}
153
257
@@ -179,6 +283,27 @@ public int getDatabaseMinorVersion() {
179
283
return metadata .minorVersion ();
180
284
}
181
285
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
+
182
307
@ Override
183
308
public String getDriverName () {
184
309
return getDatabaseName ();
@@ -196,6 +321,7 @@ public int getDriverMinorVersion() {
196
321
197
322
@ Override
198
323
public String getSQLKeywords () {
324
+ // Vert.x metadata doesn't have this info
199
325
return null ;
200
326
}
201
327
0 commit comments