Skip to content

Commit dfa3981

Browse files
committed
Make stored procedure and function calls through ProcedureCall API more portable
1 parent 72edfa7 commit dfa3981

31 files changed

+1313
-468
lines changed

gradle/databases.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ ext {
5050
'jdbc.user' : 'hibernate_orm_test',
5151
'jdbc.pass' : 'hibernate_orm_test',
5252
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
53-
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
53+
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0&escapeSyntaxCallMode=callIfNoReturn',
5454
'connection.init_sql' : ''
5555
],
5656
pgsql_ci : [
@@ -59,7 +59,7 @@ ext {
5959
'jdbc.user' : 'hibernate_orm_test',
6060
'jdbc.pass' : 'hibernate_orm_test',
6161
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
62-
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
62+
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0&escapeSyntaxCallMode=callIfNoReturn',
6363
'connection.init_sql' : ''
6464
],
6565
edb_ci : [
@@ -68,7 +68,7 @@ ext {
6868
'jdbc.user' : 'hibernate_orm_test',
6969
'jdbc.pass' : 'hibernate_orm_test',
7070
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
71-
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0',
71+
'jdbc.url' : 'jdbc:postgresql://' + dbHost + '/hibernate_orm_test?preparedStatementCacheQueries=0&escapeSyntaxCallMode=callIfNoReturn',
7272
'connection.init_sql' : ''
7373
],
7474
sybase_ci : [
@@ -201,7 +201,7 @@ ext {
201201
'jdbc.user' : 'root',
202202
'jdbc.pass' : '',
203203
// Disable prepared statement caching due to https://www.postgresql.org/message-id/CAEcMXhmmRd4-%2BNQbnjDT26XNdUoXdmntV9zdr8%3DTu8PL9aVCYg%40mail.gmail.com
204-
'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0',
204+
'jdbc.url' : 'jdbc:postgresql://' + dbHost + ':26257/defaultdb?sslmode=disable&preparedStatementCacheQueries=0&escapeSyntaxCallMode=callIfNoReturn',
205205
'connection.init_sql' : ''
206206
],
207207
firebird : [

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.hibernate.mapping.Column;
4848
import org.hibernate.metamodel.mapping.EntityMappingType;
4949
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
50+
import org.hibernate.procedure.internal.DB2CallableStatementSupport;
51+
import org.hibernate.procedure.spi.CallableStatementSupport;
5052
import org.hibernate.query.spi.QueryEngine;
5153
import org.hibernate.query.sqm.IntervalType;
5254
import org.hibernate.query.sqm.TemporalUnit;
@@ -733,6 +735,11 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
733735
);
734736
}
735737

738+
@Override
739+
public CallableStatementSupport getCallableStatementSupport() {
740+
return DB2CallableStatementSupport.INSTANCE;
741+
}
742+
736743
@Override
737744
public void appendBinaryLiteral(SqlAppender appender, byte[] bytes) {
738745
if ( getDB2Version().isSameOrAfter( 11 ) ) {

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
import org.hibernate.internal.util.JdbcExceptionHelper;
6262
import org.hibernate.metamodel.mapping.EntityMappingType;
6363
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
64-
import org.hibernate.procedure.internal.PostgresCallableStatementSupport;
64+
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
6565
import org.hibernate.procedure.spi.CallableStatementSupport;
6666
import org.hibernate.query.SemanticException;
6767
import org.hibernate.query.spi.QueryEngine;
@@ -954,9 +954,10 @@ public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
954954
return SelectItemReferenceStrategy.POSITION;
955955
}
956956

957+
957958
@Override
958959
public CallableStatementSupport getCallableStatementSupport() {
959-
return PostgresCallableStatementSupport.INSTANCE;
960+
return getVersion().isSameOrAfter( 11 ) ? PostgreSQLCallableStatementSupport.INSTANCE : PostgreSQLCallableStatementSupport.V10_INSTANCE;
960961
}
961962

962963
@Override

hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.AssertionFailure;
1515
import org.hibernate.CacheMode;
1616
import org.hibernate.FlushMode;
17+
import org.hibernate.Remove;
1718
import org.hibernate.annotations.CacheModeType;
1819
import org.hibernate.annotations.FlushModeType;
1920
import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor;
@@ -215,22 +216,20 @@ public static NamedProcedureCallDefinition createStoredProcedure(
215216
Supplier<RuntimeException> exceptionProducer) {
216217
List<StoredProcedureParameter> storedProcedureParameters = new ArrayList<>();
217218
List<QueryHint> queryHints = new ArrayList<>();
218-
List<String> parameterNames = new ArrayList<>();
219219
final String sqlString = builder.getSqlString().trim();
220220
if ( !sqlString.startsWith( "{" ) || !sqlString.endsWith( "}" ) ) {
221221
throw exceptionProducer.get();
222222
}
223-
final String procedureName = QueryBinder.parseJdbcCall(
223+
final JdbcCall jdbcCall = parseJdbcCall(
224224
sqlString,
225-
parameterNames,
226225
exceptionProducer
227226
);
228227

229228
AnnotationDescriptor ann = new AnnotationDescriptor( NamedStoredProcedureQuery.class );
230229
ann.setValue( "name", builder.getName() );
231-
ann.setValue( "procedureName", procedureName );
230+
ann.setValue( "procedureName", jdbcCall.callableName );
232231

233-
for ( String parameterName : parameterNames ) {
232+
for ( String parameterName : jdbcCall.parameters ) {
234233
AnnotationDescriptor parameterDescriptor = new AnnotationDescriptor( StoredProcedureParameter.class );
235234
parameterDescriptor.setValue( "name", parameterName );
236235
parameterDescriptor.setValue( "mode", ParameterMode.IN );
@@ -279,10 +278,13 @@ public static NamedProcedureCallDefinition createStoredProcedure(
279278
queryHints.add( AnnotationFactory.create( hintDescriptor ) );
280279
}
281280

282-
AnnotationDescriptor hintDescriptor2 = new AnnotationDescriptor( QueryHint.class );
283-
hintDescriptor2.setValue( "name", HibernateHints.HINT_CALLABLE_FUNCTION );
284-
hintDescriptor2.setValue( "value", "true" );
285-
queryHints.add( AnnotationFactory.create( hintDescriptor2 ) );
281+
if ( jdbcCall.resultParameter ) {
282+
// Mark native queries that have a result parameter as callable functions
283+
AnnotationDescriptor hintDescriptor2 = new AnnotationDescriptor( QueryHint.class );
284+
hintDescriptor2.setValue( "name", HibernateHints.HINT_CALLABLE_FUNCTION );
285+
hintDescriptor2.setValue( "value", "true" );
286+
queryHints.add( AnnotationFactory.create( hintDescriptor2 ) );
287+
}
286288

287289
ann.setValue( "hints", queryHints.toArray( new QueryHint[queryHints.size()] ) );
288290

@@ -461,6 +463,79 @@ public static void bindSqlResultSetMapping(
461463
context.getMetadataCollector().addSecondPass( new ResultSetMappingSecondPass( ann, context, isDefault ) );
462464
}
463465

466+
private static class JdbcCall {
467+
private final String callableName;
468+
private final boolean resultParameter;
469+
private final ArrayList<String> parameters;
470+
471+
public JdbcCall(String callableName, boolean resultParameter, ArrayList<String> parameters) {
472+
this.callableName = callableName;
473+
this.resultParameter = resultParameter;
474+
this.parameters = parameters;
475+
}
476+
}
477+
478+
private static JdbcCall parseJdbcCall(String sqlString, Supplier<RuntimeException> exceptionProducer) {
479+
String callableName = null;
480+
boolean resultParameter = false;
481+
int index = skipWhitespace( sqlString, 1 );
482+
// Parse the out param `?=` part
483+
if ( sqlString.charAt( index ) == '?' ) {
484+
resultParameter = true;
485+
index++;
486+
index = skipWhitespace( sqlString, index );
487+
if ( sqlString.charAt( index ) != '=' ) {
488+
throw exceptionProducer.get();
489+
}
490+
index++;
491+
index = skipWhitespace( sqlString, index );
492+
}
493+
// Parse the call keyword
494+
if ( !sqlString.regionMatches( true, index, "call", 0, 4 ) ) {
495+
throw exceptionProducer.get();
496+
}
497+
index += 4;
498+
index = skipWhitespace( sqlString, index );
499+
500+
// Parse the procedure name
501+
final int procedureStart = index;
502+
for ( ; index < sqlString.length(); index++ ) {
503+
final char c = sqlString.charAt( index );
504+
if ( c == '(' || Character.isWhitespace( c ) ) {
505+
callableName = sqlString.substring( procedureStart, index );
506+
break;
507+
}
508+
}
509+
index = skipWhitespace( sqlString, index );
510+
final ArrayList<String> parameters = new ArrayList<>();
511+
ParameterParser.parse(
512+
sqlString.substring( index, sqlString.length() - 1 ),
513+
new ParameterRecognizer() {
514+
@Override
515+
public void ordinalParameter(int sourcePosition) {
516+
parameters.add( "" );
517+
}
518+
519+
@Override
520+
public void namedParameter(String name, int sourcePosition) {
521+
parameters.add( name );
522+
}
523+
524+
@Override
525+
public void jpaPositionalParameter(int label, int sourcePosition) {
526+
parameters.add( "" );
527+
}
528+
529+
@Override
530+
public void other(char character) {
531+
}
532+
}
533+
);
534+
return new JdbcCall( callableName, resultParameter, parameters );
535+
}
536+
537+
@Remove
538+
@Deprecated(since = "6.2")
464539
public static String parseJdbcCall(
465540
String sqlString,
466541
List<String> parameterNames,

hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.hibernate.mapping.Column;
4141
import org.hibernate.metamodel.mapping.EntityMappingType;
4242
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
43+
import org.hibernate.procedure.internal.DB2CallableStatementSupport;
44+
import org.hibernate.procedure.spi.CallableStatementSupport;
4345
import org.hibernate.query.spi.QueryEngine;
4446
import org.hibernate.query.sqm.IntervalType;
4547
import org.hibernate.query.sqm.TemporalUnit;
@@ -749,6 +751,11 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
749751
);
750752
}
751753

754+
@Override
755+
public CallableStatementSupport getCallableStatementSupport() {
756+
return DB2CallableStatementSupport.INSTANCE;
757+
}
758+
752759
@Override
753760
public void appendBinaryLiteral(SqlAppender appender, byte[] bytes) {
754761
if ( getDB2Version().isSameOrAfter( 11 ) ) {

hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
import org.hibernate.internal.util.JdbcExceptionHelper;
4848
import org.hibernate.metamodel.mapping.EntityMappingType;
4949
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
50-
import org.hibernate.procedure.internal.PostgresCallableStatementSupport;
50+
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
5151
import org.hibernate.procedure.spi.CallableStatementSupport;
5252
import org.hibernate.query.SemanticException;
5353
import org.hibernate.query.spi.QueryEngine;
@@ -929,7 +929,7 @@ public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() {
929929

930930
@Override
931931
public CallableStatementSupport getCallableStatementSupport() {
932-
return PostgresCallableStatementSupport.INSTANCE;
932+
return getVersion().isSameOrAfter( 11 ) ? PostgreSQLCallableStatementSupport.INSTANCE : PostgreSQLCallableStatementSupport.V10_INSTANCE;
933933
}
934934

935935
@Override

hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import org.hibernate.dialect.function.CommonFunctionFactory;
1616
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
17+
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
18+
import org.hibernate.procedure.spi.CallableStatementSupport;
1719
import org.hibernate.query.sqm.CastType;
1820
import org.hibernate.query.sqm.TemporalUnit;
1921
import org.hibernate.query.spi.QueryEngine;
@@ -105,6 +107,11 @@ public ResultSet getResultSet(CallableStatement ps) throws SQLException {
105107
return (ResultSet) ps.getObject( 1 );
106108
}
107109

110+
@Override
111+
public CallableStatementSupport getCallableStatementSupport() {
112+
return getVersion().isSameOrAfter( 10 ) ? PostgreSQLCallableStatementSupport.INSTANCE : PostgreSQLCallableStatementSupport.V10_INSTANCE;
113+
}
114+
108115
@Override
109116
public String getSelectGUIDString() {
110117
return "select uuid_generate_v1";

hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
2424
import org.hibernate.engine.spi.LoadQueryInfluencers;
2525
import org.hibernate.engine.spi.SessionFactoryImplementor;
26+
import org.hibernate.procedure.internal.JTDSCallableStatementSupport;
27+
import org.hibernate.procedure.spi.CallableStatementSupport;
2628
import org.hibernate.query.spi.QueryEngine;
2729
import org.hibernate.query.spi.QueryOptions;
2830
import org.hibernate.query.spi.QueryParameterBindings;
@@ -358,4 +360,9 @@ public NameQualifierSupport getNameQualifierSupport() {
358360
public UniqueDelegate getUniqueDelegate() {
359361
return uniqueDelegate;
360362
}
363+
364+
@Override
365+
public CallableStatementSupport getCallableStatementSupport() {
366+
return jtdsDriver ? JTDSCallableStatementSupport.INSTANCE : super.getCallableStatementSupport();
367+
}
361368
}

hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,28 @@ public interface ProcedureCall
9494
*/
9595
ProcedureCall markAsFunctionCall(int sqlType);
9696

97+
/**
98+
* Mark this ProcedureCall as representing a call to a database function,
99+
* rather than a database procedure.
100+
*
101+
* @param resultType The result type for the function return
102+
*
103+
* @return {@code this}, for method chaining
104+
* @since 6.2
105+
*/
106+
ProcedureCall markAsFunctionCall(Class<?> resultType);
107+
108+
/**
109+
* Mark this ProcedureCall as representing a call to a database function,
110+
* rather than a database procedure.
111+
*
112+
* @param typeReference The result type for the function return
113+
*
114+
* @return {@code this}, for method chaining
115+
* @since 6.2
116+
*/
117+
ProcedureCall markAsFunctionCall(BasicTypeReference<?> typeReference);
118+
97119
/**
98120
* Basic form for registering a positional parameter.
99121
*

0 commit comments

Comments
 (0)