Skip to content

Commit cb30fef

Browse files
committed
HHH-8445 - Implement REF_CURSOR support for StoredProcedureQuery
1 parent 6d097d2 commit cb30fef

15 files changed

+361
-33
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import org.hibernate.internal.SessionFactoryImpl;
4141
import org.hibernate.internal.util.StringHelper;
4242
import org.hibernate.procedure.ProcedureCallMemento;
43-
import org.hibernate.procedure.internal.ParameterStrategy;
43+
import org.hibernate.procedure.spi.ParameterStrategy;
4444
import org.hibernate.procedure.internal.ProcedureCallMementoImpl;
4545
import org.hibernate.procedure.internal.Util;
4646

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@
8181
import org.hibernate.mapping.Column;
8282
import org.hibernate.metamodel.spi.TypeContributions;
8383
import org.hibernate.persister.entity.Lockable;
84+
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
85+
import org.hibernate.procedure.spi.CallableStatementSupport;
8486
import org.hibernate.service.ServiceRegistry;
8587
import org.hibernate.sql.ANSICaseFragment;
8688
import org.hibernate.sql.ANSIJoinFragment;
@@ -2696,4 +2698,10 @@ public ScrollMode defaultScrollMode() {
26962698
public boolean supportsTuplesInSubqueries() {
26972699
return true;
26982700
}
2701+
2702+
public CallableStatementSupport getCallableStatementSupport() {
2703+
// most databases do not support returning cursors (ref_cursor)...
2704+
return StandardCallableStatementSupport.NO_REF_CURSOR_INSTANCE;
2705+
}
2706+
26992707
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
4646
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
4747
import org.hibernate.internal.util.JdbcExceptionHelper;
48+
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
49+
import org.hibernate.procedure.spi.CallableStatementSupport;
4850
import org.hibernate.sql.CaseFragment;
4951
import org.hibernate.sql.DecodeCaseFragment;
5052
import org.hibernate.sql.JoinFragment;
@@ -612,4 +614,10 @@ public int getMaxAliasLength() {
612614
// Oracle's max identifier length is 30, but Hibernate needs to add "uniqueing info" so we account for that,
613615
return 20;
614616
}
617+
618+
@Override
619+
public CallableStatementSupport getCallableStatementSupport() {
620+
// Oracle supports returning cursors
621+
return StandardCallableStatementSupport.REF_CURSOR_INSTANCE;
622+
}
615623
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
4444
import org.hibernate.id.SequenceGenerator;
4545
import org.hibernate.internal.util.JdbcExceptionHelper;
46+
import org.hibernate.procedure.internal.PostgresCallableStatementSupport;
47+
import org.hibernate.procedure.spi.CallableStatementSupport;
4648
import org.hibernate.type.StandardBasicTypes;
4749
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor;
4850
import org.hibernate.type.descriptor.sql.ClobTypeDescriptor;
@@ -514,4 +516,22 @@ public String getForUpdateNowaitString() {
514516
public String getForUpdateNowaitString(String aliases) {
515517
return getForUpdateString( aliases ) + " nowait ";
516518
}
519+
520+
@Override
521+
public CallableStatementSupport getCallableStatementSupport() {
522+
return PostgresCallableStatementSupport.INSTANCE;
523+
}
524+
525+
@Override
526+
public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException {
527+
if ( position != 1 ) {
528+
throw new UnsupportedOperationException( "PostgreSQL only supports REF_CURSOR parameters as the first parameter" );
529+
}
530+
return (ResultSet) statement.getObject( 1 );
531+
}
532+
533+
@Override
534+
public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException {
535+
throw new UnsupportedOperationException( "PostgreSQL only supports accessing REF_CURSOR parameters by name" );
536+
}
517537
}

hibernate-core/src/main/java/org/hibernate/engine/jdbc/cursor/spi/RefCursorSupport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
* Contract for JDBC REF_CURSOR support.
3333
*
3434
* @author Steve Ebersole
35+
*
36+
* @since 4.3
3537
*/
3638
public interface RefCursorSupport extends Service {
3739
/**

hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@
3232

3333
import org.jboss.logging.Logger;
3434

35-
import org.hibernate.cfg.NotYetImplementedException;
3635
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
3736
import org.hibernate.engine.spi.SessionImplementor;
3837
import org.hibernate.procedure.ParameterBind;
3938
import org.hibernate.procedure.ParameterMisuseException;
39+
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
40+
import org.hibernate.procedure.spi.ParameterStrategy;
4041
import org.hibernate.type.CalendarDateType;
4142
import org.hibernate.type.CalendarTimeType;
4243
import org.hibernate.type.CalendarType;
43-
import org.hibernate.type.DateType;
4444
import org.hibernate.type.ProcedureParameterExtractionAware;
4545
import org.hibernate.type.Type;
4646

@@ -106,6 +106,9 @@ protected AbstractParameterRegistrationImpl(
106106
this( procedureCall, null, name, mode, type, hibernateType );
107107
}
108108

109+
110+
// full constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111+
109112
private AbstractParameterRegistrationImpl(
110113
ProcedureCallImpl procedureCall,
111114
Integer position,
@@ -222,10 +225,6 @@ private boolean isDateTimeType() {
222225

223226
@Override
224227
public void prepare(CallableStatement statement, int startIndex) throws SQLException {
225-
if ( mode == ParameterMode.REF_CURSOR ) {
226-
throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" );
227-
}
228-
229228
// initially set up the Type we will use for binding as the explicit type.
230229
Type typeToUse = hibernateType;
231230
int[] sqlTypesToUse = sqlTypes;
@@ -303,7 +302,7 @@ public void prepare(CallableStatement statement, int startIndex) throws SQLExcep
303302
else {
304303
session().getFactory().getServiceRegistry()
305304
.getService( RefCursorSupport.class )
306-
.registerRefCursorParameter( statement, getPosition() );
305+
.registerRefCursorParameter( statement, startIndex );
307306
}
308307
}
309308
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
5+
* indicated by the @author tags or express copyright attribution
6+
* statements applied by the authors. All third-party contributions are
7+
* distributed under license by Red Hat Inc.
8+
*
9+
* This copyrighted material is made available to anyone wishing to use, modify,
10+
* copy, or redistribute it subject to the terms and conditions of the GNU
11+
* Lesser General Public License, as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public License
19+
* along with this distribution; if not, write to:
20+
* Free Software Foundation, Inc.
21+
* 51 Franklin Street, Fifth Floor
22+
* Boston, MA 02110-1301 USA
23+
*/
24+
package org.hibernate.procedure.internal;
25+
26+
import javax.persistence.ParameterMode;
27+
import java.sql.CallableStatement;
28+
import java.sql.SQLException;
29+
import java.sql.Types;
30+
import java.util.List;
31+
32+
import org.hibernate.HibernateException;
33+
import org.hibernate.engine.spi.SessionImplementor;
34+
import org.hibernate.procedure.spi.CallableStatementSupport;
35+
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
36+
import org.hibernate.procedure.spi.ParameterStrategy;
37+
38+
/**
39+
* @author Steve Ebersole
40+
*/
41+
public class PostgresCallableStatementSupport implements CallableStatementSupport {
42+
/**
43+
* Singleton access
44+
*/
45+
public static final PostgresCallableStatementSupport INSTANCE = new PostgresCallableStatementSupport();
46+
47+
@Override
48+
public String renderCallableStatement(
49+
String procedureName,
50+
ParameterStrategy parameterStrategy,
51+
List<ParameterRegistrationImplementor<?>> parameterRegistrations,
52+
SessionImplementor session) {
53+
// if there are any parameters, see if the first is REF_CURSOR
54+
final boolean firstParamIsRefCursor = ! parameterRegistrations.isEmpty()
55+
&& parameterRegistrations.get( 0 ).getMode() == ParameterMode.REF_CURSOR;
56+
57+
if ( firstParamIsRefCursor ) {
58+
// validate that the parameter strategy is positional (cannot mix, and REF_CURSOR is inherently positional)
59+
if ( parameterStrategy == ParameterStrategy.NAMED ) {
60+
throw new HibernateException( "Cannot mix named parameters and REF_CURSOR parameter on PostgreSQL" );
61+
}
62+
}
63+
64+
final StringBuilder buffer;
65+
if ( firstParamIsRefCursor ) {
66+
buffer = new StringBuilder().append( "{? = call " );
67+
}
68+
else {
69+
buffer = new StringBuilder().append( "{call " );
70+
}
71+
72+
buffer.append( procedureName ).append( "(" );
73+
74+
String sep = "";
75+
76+
// skip the first registration if it was a REF_CURSOR
77+
final int startIndex = firstParamIsRefCursor ? 1 : 0;
78+
for ( int i = startIndex; i < parameterRegistrations.size(); i++ ) {
79+
final ParameterRegistrationImplementor parameter = parameterRegistrations.get( i );
80+
81+
// any additional REF_CURSOR parameter registrations are an error
82+
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
83+
throw new HibernateException( "PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" );
84+
}
85+
86+
for ( int ignored : parameter.getSqlTypes() ) {
87+
buffer.append( sep ).append( "?" );
88+
sep = ",";
89+
}
90+
}
91+
92+
return buffer.append( ")}" ).toString();
93+
}
94+
95+
@Override
96+
public void registerParameters(
97+
String procedureName,
98+
CallableStatement statement,
99+
ParameterStrategy parameterStrategy,
100+
List<ParameterRegistrationImplementor<?>> parameterRegistrations,
101+
SessionImplementor session) {
102+
// prepare parameters
103+
int i = 1;
104+
105+
try {
106+
for ( ParameterRegistrationImplementor parameter : parameterRegistrations ) {
107+
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
108+
statement.registerOutParameter( i, Types.OTHER );
109+
i++;
110+
111+
}
112+
else {
113+
parameter.prepare( statement, i );
114+
i += parameter.getSqlTypes().length;
115+
}
116+
}
117+
}
118+
catch (SQLException e) {
119+
throw session.getFactory().getSQLExceptionHelper().convert(
120+
e,
121+
"Error registering CallableStatement parameters",
122+
procedureName
123+
);
124+
}
125+
}
126+
}

hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.hibernate.procedure.ProcedureCall;
5757
import org.hibernate.procedure.ProcedureCallMemento;
5858
import org.hibernate.procedure.ProcedureOutputs;
59+
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
60+
import org.hibernate.procedure.spi.ParameterStrategy;
5961
import org.hibernate.result.spi.ResultContext;
6062
import org.hibernate.type.Type;
6163

@@ -393,32 +395,19 @@ private ProcedureOutputsImpl buildOutputs() {
393395
// both: (1) add the `? = ` part and also (2) register a REFCURSOR parameter for DBs (Oracle, PGSQL) that
394396
// need it.
395397

396-
final StringBuilder buffer = new StringBuilder().append( "{call " )
397-
.append( procedureName )
398-
.append( "(" );
399-
String sep = "";
400-
for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
401-
if ( parameter == null ) {
402-
throw new QueryException( "Registered stored procedure parameters had gaps" );
403-
}
404-
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
405-
buffer.append( sep ).append( "?" );
406-
sep = ",";
407-
}
408-
else {
409-
for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) {
410-
buffer.append( sep ).append( "?" );
411-
sep = ",";
412-
}
413-
}
414-
}
415-
buffer.append( ")}" );
398+
final String call = session().getFactory().getDialect().getCallableStatementSupport().renderCallableStatement(
399+
procedureName,
400+
parameterStrategy,
401+
registeredParameters,
402+
session()
403+
);
416404

417405
try {
418406
final CallableStatement statement = (CallableStatement) getSession().getTransactionCoordinator()
419407
.getJdbcCoordinator()
420408
.getStatementPreparer()
421-
.prepareStatement( buffer.toString(), true );
409+
.prepareStatement( call, true );
410+
422411

423412
// prepare parameters
424413
int i = 1;
@@ -444,7 +433,6 @@ private ProcedureOutputsImpl buildOutputs() {
444433
}
445434
}
446435

447-
448436
@Override
449437
public Type[] getReturnTypes() throws HibernateException {
450438
throw new NotYetImplementedException();

hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.hibernate.engine.spi.SessionImplementor;
3434
import org.hibernate.procedure.ProcedureCall;
3535
import org.hibernate.procedure.ProcedureCallMemento;
36+
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
37+
import org.hibernate.procedure.spi.ParameterStrategy;
3638
import org.hibernate.type.Type;
3739

3840
/**

hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
3030
import org.hibernate.procedure.ParameterRegistration;
3131
import org.hibernate.procedure.ProcedureOutputs;
32+
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
3233
import org.hibernate.result.Output;
3334
import org.hibernate.result.internal.OutputsImpl;
3435

0 commit comments

Comments
 (0)