From fb8186d3e8b1ba532c6b248cf433235ed2379494 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 24 Dec 2021 00:21:48 +0100 Subject: [PATCH] Cleanup stored procedure handling and add support for stored procedure function return --- .../org/hibernate/annotations/QueryHints.java | 5 + ...tractStandardCallableStatementSupport.java | 42 +-- .../internal/FunctionReturnImpl.java | 48 ++- .../PostgresCallableStatementSupport.java | 57 ++-- .../procedure/internal/ProcedureCallImpl.java | 314 ++++++++++++------ .../internal/ProcedureOutputsImpl.java | 87 ++--- .../internal/ProcedureParameterImpl.java | 233 ++++++------- .../StandardCallableStatementSupport.java | 55 +-- .../hibernate/procedure/internal/Util.java | 6 +- .../spi/CallableStatementSupport.java | 11 +- .../spi/FunctionReturnImplementor.java | 13 +- .../spi/ProcedureCallImplementor.java | 15 +- .../spi/ProcedureParameterImplementor.java | 17 +- .../query/procedure/ProcedureParameter.java | 32 +- .../exec/internal/AbstractJdbcParameter.java | 24 +- .../internal/JdbcCallFunctionReturnImpl.java | 10 +- .../sql/exec/internal/JdbcCallImpl.java | 11 +- .../JdbcCallParameterExtractorImpl.java | 12 +- .../JdbcCallParameterRegistrationImpl.java | 6 +- .../JdbcCallRefCursorExtractorImpl.java | 1 - .../exec/spi/JdbcCallParameterExtractor.java | 3 +- .../sql/exec/spi/JdbcParameterBinder.java | 3 + .../JdbcValuesMappingProducerStandard.java | 1 + .../procedure/MySQLStoredProcedureTest.java | 4 +- .../procedure/OracleStoredProcedureTest.java | 17 + .../PostgreSQLStoredProcedureTest.java | 4 +- .../StoredProcedureParameterTypeTest.java | 2 +- .../orm/test/sql/refcursor/NumValue.java | 5 +- .../org/hibernate/test/procedure/Person.java | 13 + migration-guide.adoc | 21 +- 30 files changed, 573 insertions(+), 499 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java index 5665798d29..cd20e7f815 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java @@ -43,6 +43,11 @@ public class QueryHints { */ public static final String CACHEABLE = "org.hibernate.cacheable"; + /** + * Is the procedure a function? Note: only valid for named stored procedures. + */ + public static final String CALLABLE_FUNCTION = "org.hibernate.callableFunction"; + /** * Defines a comment to be applied to the SQL sent to the database. * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractStandardCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractStandardCallableStatementSupport.java index 75e1de3171..79adefd917 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractStandardCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractStandardCallableStatementSupport.java @@ -7,57 +7,29 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; -import java.sql.SQLException; -import java.util.List; -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.procedure.spi.ProcedureCallImplementor; -import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; - -import jakarta.persistence.ParameterMode; +import org.hibernate.sql.exec.spi.JdbcCall; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; public abstract class AbstractStandardCallableStatementSupport implements CallableStatementSupport { @Override public void registerParameters( String procedureName, - ProcedureCallImplementor procedureCall, + JdbcCall procedureCall, CallableStatement statement, ParameterStrategy parameterStrategy, ProcedureParameterMetadataImplementor parameterMetadata, SharedSessionContractImplementor session) { - - final List> registrations = parameterMetadata.getRegistrationsAsList(); - final RefCursorSupport refCursorSupport = procedureCall.getSession().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ); - try { - for ( int i = 0; i < registrations.size(); i++ ) { - final ProcedureParameterImplementor parameter = registrations.get( i ); - if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { - refCursorSupport - .registerRefCursorParameter( statement, parameter.getName() ); - } - else { - refCursorSupport - .registerRefCursorParameter( statement, parameter.getPosition() ); - } - } - else { - parameter.prepare( statement, i + 1, procedureCall ); - } - } + if ( procedureCall.getFunctionReturn() != null ) { + procedureCall.getFunctionReturn().registerParameter( statement, session ); } - catch (SQLException e) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( - e, - "Error registering CallableStatement parameters", - procedureName - ); + for ( JdbcCallParameterRegistration parameterRegistration : procedureCall.getParameterRegistrations() ) { + parameterRegistration.registerParameter( statement, session ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java index 8f9030473f..ac59ee47d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java @@ -7,10 +7,7 @@ package org.hibernate.procedure.internal; -import java.sql.CallableStatement; -import java.sql.SQLException; import java.sql.Types; -import jakarta.persistence.ParameterMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -27,51 +24,52 @@ import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; +import jakarta.persistence.ParameterMode; + /** * @author Steve Ebersole */ -public class FunctionReturnImpl implements FunctionReturnImplementor { - private final ProcedureCallImplementor procedureCall; - private int jdbcTypeCode; +public class FunctionReturnImpl implements FunctionReturnImplementor { + private final ProcedureCallImplementor procedureCall; + private final int jdbcTypeCode; - private AllowableOutputParameterType ormType; + private AllowableOutputParameterType ormType; - public FunctionReturnImpl(ProcedureCallImplementor procedureCall, int jdbcTypeCode) { + public FunctionReturnImpl(ProcedureCallImplementor procedureCall, int jdbcTypeCode) { this.procedureCall = procedureCall; this.jdbcTypeCode = jdbcTypeCode; } - public FunctionReturnImpl(ProcedureCallImplementor procedureCall, AllowableOutputParameterType ormType) { + public FunctionReturnImpl(ProcedureCallImplementor procedureCall, AllowableOutputParameterType ormType) { this.procedureCall = procedureCall; this.jdbcTypeCode = ormType.getJdbcTypeDescriptor().getJdbcTypeCode(); - this.ormType = ormType; } - + @Override public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) { - final AllowableParameterType ormType; + final AllowableParameterType ormType; final JdbcCallRefCursorExtractorImpl refCursorExtractor; - final JdbcCallParameterExtractorImpl parameterExtractor; + final JdbcCallParameterExtractorImpl parameterExtractor; if ( getJdbcTypeCode() == Types.REF_CURSOR ) { - refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 0 ); + refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 1 ); ormType = null; parameterExtractor = null; } else { - final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getMetamodel().getTypeConfiguration(); final JdbcType sqlTypeDescriptor = typeConfiguration.getJdbcTypeDescriptorRegistry() .getDescriptor( getJdbcTypeCode() ); final BasicJavaType javaTypeMapping = sqlTypeDescriptor .getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ); - ormType = typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaTypeClass() ); - parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), null, 0, ormType ); + //noinspection unchecked + ormType = (AllowableParameterType) typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaTypeClass() ); + parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), null, 1, ormType ); refCursorExtractor = null; } - return new JdbcCallFunctionReturnImpl( getJdbcTypeCode(), ormType, parameterExtractor, refCursorExtractor ); + return new JdbcCallFunctionReturnImpl( ormType, parameterExtractor, refCursorExtractor ); } @Override @@ -80,7 +78,7 @@ public class FunctionReturnImpl implements FunctionReturnImplementor { } @Override - public AllowableParameterType getHibernateType() { + public AllowableParameterType getHibernateType() { return ormType; } @@ -91,7 +89,7 @@ public class FunctionReturnImpl implements FunctionReturnImplementor { @Override public Integer getPosition() { - return 0; + return 1; } @Override @@ -125,17 +123,11 @@ public class FunctionReturnImpl implements FunctionReturnImplementor { public NamedCallableQueryMemento.ParameterMemento toMemento() { return session -> { if ( ormType != null ) { - return new FunctionReturnImpl( procedureCall, ormType ); + return new FunctionReturnImpl<>( procedureCall, ormType ); } else { - return new FunctionReturnImpl( procedureCall, jdbcTypeCode ); + return new FunctionReturnImpl<>( procedureCall, jdbcTypeCode ); } }; } - - @Override - public void prepare(CallableStatement statement, int startIndex, ProcedureCallImplementor callImplementor) - throws SQLException { - throw new NotYetImplementedFor6Exception( getClass() ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgresCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgresCallableStatementSupport.java index e93196f024..51c4452e92 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgresCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgresCallableStatementSupport.java @@ -10,7 +10,9 @@ import java.util.List; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.procedure.spi.FunctionReturnImplementor; import org.hibernate.procedure.spi.ParameterStrategy; +import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.sql.exec.internal.JdbcCallImpl; @@ -28,12 +30,11 @@ public class PostgresCallableStatementSupport extends AbstractStandardCallableSt public static final PostgresCallableStatementSupport INSTANCE = new PostgresCallableStatementSupport(); @Override - public JdbcCall interpretCall( - String procedureName, - FunctionReturnImpl functionReturn, - ProcedureParameterMetadataImplementor parameterMetadata, - ProcedureParamBindings paramBindings, - SharedSessionContractImplementor session) { + public JdbcCall interpretCall(ProcedureCallImplementor procedureCall) { + final String procedureName = procedureCall.getProcedureName(); + final FunctionReturnImplementor functionReturn = procedureCall.getFunctionReturn(); + final ProcedureParameterMetadataImplementor parameterMetadata = procedureCall.getParameterMetadata(); + final SharedSessionContractImplementor session = procedureCall.getSession(); final boolean firstParamIsRefCursor = parameterMetadata.getParameterCount() != 0 && isFirstParameterModeRefCursor( parameterMetadata ); @@ -45,42 +46,50 @@ public class PostgresCallableStatementSupport extends AbstractStandardCallableSt } final List> registrations = parameterMetadata.getRegistrationsAsList(); + final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder( + parameterMetadata.hasNamedParameters() ? + ParameterStrategy.NAMED : + ParameterStrategy.POSITIONAL + ); final StringBuilder buffer; - if ( firstParamIsRefCursor ) { - buffer = new StringBuilder(11 + procedureName.length() + registrations.size() * 2).append( "{?=call " ); + final int offset; + final int startIndex; + if ( functionReturn != null ) { + offset = 2; + startIndex = 0; + buffer = new StringBuilder( 11 + procedureName.length() + registrations.size() * 2 ).append( "{?=call " ); + builder.setFunctionReturn( functionReturn.toJdbcFunctionReturn( session ) ); + } + else if ( firstParamIsRefCursor ) { + offset = 1; + startIndex = 1; + buffer = new StringBuilder( 11 + procedureName.length() + registrations.size() * 2 ).append( "{?=call " ); + builder.addParameterRegistration( registrations.get( 0 ).toJdbcParameterRegistration( 1, procedureCall ) ); } else { - buffer = new StringBuilder(9 + procedureName.length() + registrations.size() * 2).append( "{call " ); + offset = 1; + startIndex = 0; + buffer = new StringBuilder( 9 + procedureName.length() + registrations.size() * 2 ).append( "{call " ); } buffer.append( procedureName ).append( "(" ); - // skip the first registration if it was a REF_CURSOR - final int startIndex; - if ( firstParamIsRefCursor ) { - startIndex = 1; - } - else { - startIndex = 0; - } String sep = ""; for ( int i = startIndex; i < registrations.size(); i++ ) { - if ( registrations.get( i ).getMode() == ParameterMode.REF_CURSOR ) { + final ProcedureParameterImplementor parameter = registrations.get( i ); + if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { throw new HibernateException( "PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" ); } buffer.append( sep ).append( "?" ); sep = ","; + builder.addParameterRegistration( parameter.toJdbcParameterRegistration( i + offset, procedureCall ) ); } buffer.append( ")}" ); - return new JdbcCallImpl.Builder( - buffer.toString(), - parameterMetadata.hasNamedParameters() ? - ParameterStrategy.NAMED : - ParameterStrategy.POSITIONAL - ).buildJdbcCall(); + builder.setCallableName( buffer.toString() ); + return builder.buildJdbcCall(); } private static boolean isFirstParameterModeRefCursor(ProcedureParameterMetadataImplementor parameterMetadata) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index ee6df36375..049862d4d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -7,15 +7,83 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; + +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.ScrollMode; +import org.hibernate.annotations.QueryHints; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.graph.RootGraph; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.procedure.NoSuchParameterException; +import org.hibernate.procedure.ParameterStrategyException; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureOutputs; +import org.hibernate.procedure.spi.CallableStatementSupport; +import org.hibernate.procedure.spi.FunctionReturnImplementor; +import org.hibernate.procedure.spi.NamedCallableQueryMemento; +import org.hibernate.procedure.spi.ParameterStrategy; +import org.hibernate.procedure.spi.ProcedureCallImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.Query; +import org.hibernate.query.QueryParameter; +import org.hibernate.query.internal.QueryOptionsImpl; +import org.hibernate.query.named.NamedQueryMemento; +import org.hibernate.query.procedure.ProcedureParameter; +import org.hibernate.query.results.ResultSetMapping; +import org.hibernate.query.results.ResultSetMappingImpl; +import org.hibernate.query.spi.AbstractQuery; +import org.hibernate.query.spi.MutableQueryOptions; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryOptionsAdapter; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.result.NoMoreReturnsException; +import org.hibernate.result.Output; +import org.hibernate.result.ResultSetOutput; +import org.hibernate.result.UpdateCountOutput; +import org.hibernate.result.spi.ResultContext; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.internal.CallbackImpl; +import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcCall; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; +import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.results.NoMoreOutputsException; +import org.hibernate.type.BasicType; +import org.hibernate.type.BasicTypeReference; +import org.hibernate.type.spi.TypeConfiguration; + +import org.jboss.logging.Logger; + import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.NoResultException; @@ -26,50 +94,6 @@ import jakarta.persistence.PersistenceException; import jakarta.persistence.TemporalType; import jakarta.persistence.TransactionRequiredException; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.ScrollMode; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.graph.GraphSemantic; -import org.hibernate.graph.RootGraph; -import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.model.domain.AllowableParameterType; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.procedure.NoSuchParameterException; -import org.hibernate.procedure.ParameterStrategyException; -import org.hibernate.procedure.ProcedureCall; -import org.hibernate.procedure.ProcedureOutputs; -import org.hibernate.procedure.spi.CallableStatementSupport; -import org.hibernate.procedure.spi.NamedCallableQueryMemento; -import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.procedure.spi.ProcedureCallImplementor; -import org.hibernate.procedure.spi.ProcedureParameterImplementor; -import org.hibernate.query.Query; -import org.hibernate.query.QueryParameter; -import org.hibernate.query.internal.QueryOptionsImpl; -import org.hibernate.query.procedure.ProcedureParameter; -import org.hibernate.query.results.ResultSetMapping; -import org.hibernate.query.results.ResultSetMappingImpl; -import org.hibernate.query.spi.AbstractQuery; -import org.hibernate.query.spi.MutableQueryOptions; -import org.hibernate.query.spi.QueryImplementor; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.spi.ScrollableResultsImplementor; -import org.hibernate.result.NoMoreReturnsException; -import org.hibernate.result.Output; -import org.hibernate.result.ResultSetOutput; -import org.hibernate.result.UpdateCountOutput; -import org.hibernate.result.spi.ResultContext; -import org.hibernate.sql.exec.spi.JdbcCall; -import org.hibernate.sql.results.NoMoreOutputsException; -import org.hibernate.type.BasicType; -import org.hibernate.type.BasicTypeReference; - -import org.jboss.logging.Logger; - /** * Standard implementation of {@link ProcedureCall} * @@ -82,7 +106,7 @@ public class ProcedureCallImpl private final String procedureName; - private FunctionReturnImpl functionReturn; + private FunctionReturnImpl functionReturn; private final ProcedureParameterMetadataImpl parameterMetadata; private final ProcedureParamBindings paramBindings; @@ -93,6 +117,7 @@ public class ProcedureCallImpl private final QueryOptionsImpl queryOptions = new QueryOptionsImpl(); + private JdbcCall call; private ProcedureOutputsImpl outputs; @@ -263,6 +288,30 @@ public class ProcedureCallImpl synchronizedQuerySpaces::add, () -> getSession().getFactory() ); + + applyOptions( memento ); + } + + protected void applyOptions(NamedCallableQueryMemento memento) { + applyOptions( (NamedQueryMemento) memento ); + + if ( memento.getHints() != null ) { + final Object callableFunction = memento.getHints().get( QueryHints.CALLABLE_FUNCTION ); + if ( callableFunction != null && Boolean.parseBoolean( callableFunction.toString() ) ) { + final List> resultTypes = new ArrayList<>(); + resultSetMapping.visitResultBuilders( + (index, resultBuilder) -> resultTypes.add( resultBuilder.getJavaType() ) + ); + final TypeConfiguration typeConfiguration = getSessionFactory().getTypeConfiguration(); + final BasicType type; + if ( resultTypes.size() != 1 || ( type = typeConfiguration.getBasicTypeForJavaType( resultTypes.get( 0 ) ) ) == null ) { + markAsFunctionCall( Types.REF_CURSOR ); + } + else { + markAsFunctionCall( type.getJdbcTypeDescriptor().getJdbcTypeCode() ); + } + } + } } @Override @@ -299,9 +348,14 @@ public class ProcedureCallImpl return functionReturn != null; } + @Override + public FunctionReturnImplementor getFunctionReturn() { + return functionReturn; + } + @Override public ProcedureCall markAsFunctionCall(int sqlType) { - functionReturn = new FunctionReturnImpl( this, sqlType ); + functionReturn = new FunctionReturnImpl<>( this, sqlType ); return this; } @@ -519,15 +573,27 @@ public class ProcedureCallImpl .getJdbcEnvironment() .getDialect() .getCallableStatementSupport(); - final ProcedureParameterMetadataImpl parameterMetadata = getParameterMetadata(); - final JdbcCall call = callableStatementSupport.interpretCall( - procedureName, - functionReturn, - parameterMetadata, - paramBindings, - getSession() - ); + this.call = callableStatementSupport.interpretCall( this ); + final Map, JdbcCallParameterRegistration> parameterRegistrations = new IdentityHashMap<>(); + final List refCursorExtractors = new ArrayList<>(); + if ( functionReturn != null ) { + parameterRegistrations.put( functionReturn, call.getFunctionReturn() ); + final JdbcCallRefCursorExtractorImpl refCursorExtractor = call.getFunctionReturn().getRefCursorExtractor(); + if ( refCursorExtractor != null ) { + refCursorExtractors.add( refCursorExtractor ); + } + } + final List> registrations = getParameterMetadata().getRegistrationsAsList(); + final List jdbcParameters = call.getParameterRegistrations(); + for ( int i = 0; i < registrations.size(); i++ ) { + final JdbcCallParameterRegistration jdbcCallParameterRegistration = jdbcParameters.get( i ); + parameterRegistrations.put( registrations.get( i ), jdbcCallParameterRegistration ); + final JdbcCallRefCursorExtractorImpl refCursorExtractor = jdbcCallParameterRegistration.getRefCursorExtractor(); + if ( refCursorExtractor != null ) { + refCursorExtractors.add( refCursorExtractor ); + } + } LOG.debugf( "Preparing procedure call : %s", call ); final CallableStatement statement = (CallableStatement) getSession() @@ -535,35 +601,100 @@ public class ProcedureCallImpl .getStatementPreparer() .prepareStatement( call.getSql(), true ); - callableStatementSupport.registerParameters( procedureName, this,statement, parameterMetadata.getParameterStrategy(), parameterMetadata, getSession() ); -// getParameterMetadata().visitRegistrations( -// new Consumer>() { -// int i = 1; -// -// @Override -// public void accept(QueryParameter queryParameter) { -// try { -// final ProcedureParameterImplementor registration = (ProcedureParameterImplementor) queryParameter; -// registration.prepare( statement, i, ProcedureCallImpl.this ); -//// if ( registration.getMode() == ParameterMode.REF_CURSOR ) { -// i++; -//// } -//// else { -//// i += registration.getHibernateType().getSqlTypes().length; -//// } -// } -// catch (SQLException e) { -// throw getSession().getJdbcServices().getSqlExceptionHelper().convert( -// e, -// "Error preparing registered callable parameter", -// getProcedureName() -// ); -// } -// } -// } -// ); + // Register the parameter mode and type + callableStatementSupport.registerParameters( + procedureName, + call, + statement, + parameterMetadata.getParameterStrategy(), + parameterMetadata, + getSession() + ); - return new ProcedureOutputsImpl( this, statement ); + // Apply the parameter bindings + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( parameterRegistrations.size() ); + for ( Map.Entry, JdbcCallParameterRegistration> entry : parameterRegistrations.entrySet() ) { + final JdbcCallParameterRegistration registration = entry.getValue(); + if ( registration.getParameterBinder() != null ) { + final ProcedureParameter parameter = entry.getKey(); + final QueryParameterBinding binding = getParameterBindings().getBinding( parameter ); + if ( !binding.isBound() ) { + if ( parameter.getPosition() == null ) { + throw new IllegalArgumentException( "The parameter named [" + parameter + "] was not set! You need to call the setParameter method." ); + } + else { + throw new IllegalArgumentException( "The parameter at position [" + parameter + "] was not set! You need to call the setParameter method." ); + } + } + jdbcParameterBindings.addBinding( + (JdbcParameter) registration.getParameterBinder(), + new JdbcParameterBindingImpl( + (JdbcMapping) registration.getParameterType(), + binding.getBindValue() + ) + ); + } + } + + final JdbcCallRefCursorExtractor[] extractors = refCursorExtractors.toArray( new JdbcCallRefCursorExtractor[0] ); + + final ExecutionContext executionContext = new ExecutionContext() { + private final Callback callback = new CallbackImpl(); + + @Override + public SharedSessionContractImplementor getSession() { + return ProcedureCallImpl.this.getSession(); + } + + @Override + public QueryOptions getQueryOptions() { + return new QueryOptionsAdapter() { + @Override + public Boolean isReadOnly() { + return false; + } + }; + } + + @Override + public String getQueryIdentifier(String sql) { + return sql; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return QueryParameterBindings.NO_PARAM_BINDINGS; + } + + @Override + public Callback getCallback() { + return callback; + } + + }; + + // Note that this should actually happen in an executor + + try { + int paramBindingPosition = functionReturn == null ? 1 : 2; + for ( JdbcParameterBinder parameterBinder : call.getParameterBinders() ) { + parameterBinder.bindParameterValue( + statement, + paramBindingPosition, + jdbcParameterBindings, + executionContext + ); + paramBindingPosition++; + } + } + catch (SQLException e) { + throw getSession().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Error registering CallableStatement parameters", + procedureName + ); + } + return new ProcedureOutputsImpl( this, parameterRegistrations, extractors, statement ); } @Override @@ -618,25 +749,6 @@ public class ProcedureCallImpl return this; } - /** - * Collects any parameter registrations which indicate a REF_CURSOR parameter type/mode. - * - * @return The collected REF_CURSOR type parameters. - */ - public ProcedureParameterImplementor[] collectRefCursorParameters() { - final List refCursorParams = new ArrayList<>(); - - getParameterMetadata().visitRegistrations( - queryParameter -> { - final ProcedureParameterImplementor registration = (ProcedureParameterImplementor) queryParameter; - if ( registration.getMode() == ParameterMode.REF_CURSOR ) { - refCursorParams.add( registration ); - } - } - ); - return refCursorParams.toArray( new ProcedureParameterImplementor[0] ); - } - @Override public NamedCallableQueryMemento toMemento(String name) { return new NamedCallableQueryMementoImpl( diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index 8a1d26a3ac..0fd96c8471 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -8,19 +8,16 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; import java.sql.ResultSet; -import java.sql.SQLException; +import java.util.Map; -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.metamodel.model.domain.AllowableOutputParameterType; -import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.procedure.ParameterMisuseException; import org.hibernate.procedure.ProcedureOutputs; -import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.result.Output; import org.hibernate.result.internal.OutputsImpl; import org.hibernate.sql.exec.ExecutionException; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; +import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor; import jakarta.persistence.ParameterMode; @@ -33,60 +30,51 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput private final ProcedureCallImpl procedureCall; private final CallableStatement callableStatement; - private final ProcedureParameterImplementor[] refCursorParameters; + private final Map, JdbcCallParameterRegistration> parameterRegistrations; + private final JdbcCallRefCursorExtractor[] refCursorParameters; private int refCursorParamIndex; - ProcedureOutputsImpl(ProcedureCallImpl procedureCall, CallableStatement callableStatement) { + ProcedureOutputsImpl( + ProcedureCallImpl procedureCall, + Map, JdbcCallParameterRegistration> parameterRegistrations, + JdbcCallRefCursorExtractor[] refCursorParameters, + CallableStatement callableStatement) { super( procedureCall, callableStatement ); this.procedureCall = procedureCall; this.callableStatement = callableStatement; - - this.refCursorParameters = procedureCall.collectRefCursorParameters(); + this.parameterRegistrations = parameterRegistrations; + this.refCursorParameters = refCursorParameters; } @Override public T getOutputParameterValue(ProcedureParameter parameter) { - final AllowableParameterType hibernateType = parameter.getHibernateType(); if ( parameter.getMode() == ParameterMode.IN ) { throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); } + final JdbcCallParameterRegistration registration = parameterRegistrations.get( parameter ); + if ( registration == null ) { + throw new IllegalArgumentException( "Parameter [" + parameter + "] is not registered with this procedure call" ); + } try { - if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { - if ( parameter.getPosition() != null ) { - return (T) callableStatement.getObject( parameter.getPosition() ); - } - else { - return (T) callableStatement.getObject( parameter.getName() ); - } - } - else if ( hibernateType instanceof AllowableOutputParameterType ) { - + if ( registration.getParameterMode() == ParameterMode.REF_CURSOR ) { //noinspection unchecked - if ( parameter.getPosition() != null ) { - return (T) ( (AllowableOutputParameterType) hibernateType ).extract( - callableStatement, - parameter.getPosition(), - procedureCall.getSession() - ); - } - else { - return (T) ( (AllowableOutputParameterType) hibernateType ).extract( - callableStatement, - parameter.getName(), - procedureCall.getSession() - ); - } + return (T) registration.getRefCursorExtractor().extractResultSet( + callableStatement, + procedureCall.getSession() + ); } else { - throw new ParameterMisuseException( "Parameter type cannot extract procedure output parameters" ); + //noinspection unchecked + return (T) registration.getParameterExtractor().extractValue( + callableStatement, + parameter.getPosition() == null, + procedureCall.getSession() + ); } } - catch (SQLException e) { + catch (Exception e) { throw new ExecutionException( - "Error extracting procedure output parameter value [" - + parameter.getPosition() != null ? - String.valueOf( parameter.getPosition() ) : - parameter.getName() + "]", + "Error extracting procedure output parameter value [" + parameter + "]", e ); } @@ -118,7 +106,7 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput @Override public boolean indicatesMoreOutputs() { return super.indicatesMoreOutputs() - || ProcedureOutputsImpl.this.refCursorParamIndex < ProcedureOutputsImpl.this.refCursorParameters.length; + || ProcedureOutputsImpl.this.refCursorParamIndex < refCursorParameters.length; } @Override @@ -128,19 +116,8 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput @Override protected Output buildExtendedReturn() { - ProcedureOutputsImpl.this.refCursorParamIndex++; - final ProcedureParameterImplementor refCursorParam = ProcedureOutputsImpl.this.refCursorParameters[refCursorParamIndex]; - ResultSet resultSet; - if ( refCursorParam.getName() != null ) { - resultSet = ProcedureOutputsImpl.this.procedureCall.getSession().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .getResultSet( ProcedureOutputsImpl.this.callableStatement, refCursorParam.getName() ); - } - else { - resultSet = ProcedureOutputsImpl.this.procedureCall.getSession().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .getResultSet( ProcedureOutputsImpl.this.callableStatement, refCursorParam.getPosition() ); - } + final JdbcCallRefCursorExtractor refCursorParam = refCursorParameters[ProcedureOutputsImpl.this.refCursorParamIndex++]; + final ResultSet resultSet = refCursorParam.extractResultSet( callableStatement, procedureCall.getSession() ); return buildResultSetOutput( () -> extractResults( resultSet ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java index b416078cef..ec187a1829 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java @@ -7,13 +7,14 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Objects; -import jakarta.persistence.ParameterMode; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; @@ -22,28 +23,30 @@ import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.AbstractQueryParameter; import org.hibernate.query.internal.BindingTypeHelper; import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.sql.exec.internal.JdbcCallParameterExtractorImpl; +import org.hibernate.sql.exec.internal.JdbcCallParameterRegistrationImpl; +import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.type.BasicType; import org.hibernate.type.ProcedureParameterNamedBinder; -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; -import org.jboss.logging.Logger; +import jakarta.persistence.ParameterMode; /** * @author Steve Ebersole */ public class ProcedureParameterImpl extends AbstractQueryParameter implements ProcedureParameterImplementor { - private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class ); private final String name; - private Integer position; - + private final Integer position; private final ParameterMode mode; - private final Class javaType; - public ProcedureParameterImpl( String name, ParameterMode mode, @@ -111,29 +114,9 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme } @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( o == null || getClass() != o.getClass() ) { - return false; - } - ProcedureParameterImpl that = (ProcedureParameterImpl) o; - return Objects.equals( name, that.name ) && - Objects.equals( position, that.position ) && - mode == that.mode; - } - - @Override - public int hashCode() { - return Objects.hash( name, position, mode ); - } - - @Override - public void prepare( - CallableStatement statement, + public JdbcCallParameterRegistration toJdbcParameterRegistration( int startIndex, - ProcedureCallImplementor procedureCall) throws SQLException { + ProcedureCallImplementor procedureCall) { final QueryParameterBinding binding = procedureCall.getParameterBindings().getBinding( this ); final TypeConfiguration typeConfiguration = procedureCall.getSession().getFactory().getTypeConfiguration(); final AllowableParameterType typeToUse = BindingTypeHelper.INSTANCE.resolveTemporalPrecision( @@ -144,115 +127,72 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme typeConfiguration ); - if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - -// if ( sqlTypesToUse.length > 1 ) { -// // there is more than one column involved; see if the Hibernate Type can handle -// // multi-param extraction... -// final boolean canHandleMultiParamExtraction = -// ProcedureParameterExtractionAware.class.isInstance( typeToUse ) -// && ( (ProcedureParameterExtractionAware) typeToUse ).canDoExtraction(); -// if ( ! canHandleMultiParamExtraction ) { -// // it cannot... -// throw new UnsupportedOperationException( -// "Type [" + typeToUse + "] does support multi-parameter value extraction" -// ); -// } -// } - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // The idea is that an embeddable/custom type can have more than one column values - // that correspond with embeddable/custom attribute value. This does not seem to - // be working yet. For now, if sqlTypesToUse.length > 1, then register - // the out parameters by position (since we only have one name). - // This will cause a failure if there are other parameters bound by - // name and the dialect does not support "mixed" named/positional parameters; - // e.g., Oracle. - final JdbcType recommendedJdbcType = typeToUse.getExpressableJavaTypeDescriptor() - .getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() ); - - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding( typeToUse, procedureCall ) ) { - statement.registerOutParameter( getName(), recommendedJdbcType.getJdbcTypeCode() ); - } - else { -// for ( int i = 0; i < sqlTypesToUse.length; i++ ) { - if ( position == null ) { - position = startIndex; - } - statement.registerOutParameter( startIndex, recommendedJdbcType.getJdbcTypeCode() ); -// } - } + final String name; + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED + && canDoNameParameterBinding( typeToUse, procedureCall ) ) { + name = this.name; + } + else { + name = null; } - if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { - final ValueBinder binder; - final BasicType basicType; - if ( typeToUse instanceof BasicType ) { - basicType = ( (BasicType) typeToUse ); - binder = basicType.getJdbcValueBinder(); + final JdbcParameterBinder parameterBinder; + final JdbcCallRefCursorExtractorImpl refCursorExtractor; + final JdbcCallParameterExtractorImpl parameterExtractor; + + switch ( mode ) { + case REF_CURSOR: + refCursorExtractor = new JdbcCallRefCursorExtractorImpl( name, startIndex ); + parameterBinder = null; + parameterExtractor = null; + break; + case IN: + parameterBinder = getParameterBinder( typeToUse, name ); + parameterExtractor = null; + refCursorExtractor = null; + break; + case INOUT: + parameterBinder = getParameterBinder( typeToUse, name ); + parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), name, startIndex, typeToUse ); + refCursorExtractor = null; + break; + default: + parameterBinder = null; + parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), name, startIndex, typeToUse ); + refCursorExtractor = null; + break; + } + + return new JdbcCallParameterRegistrationImpl( name, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor ); + } + + private JdbcParameterBinder getParameterBinder(AllowableParameterType typeToUse, String name) { + if ( typeToUse instanceof BasicType ) { + if ( name == null ) { + return new JdbcParameterImpl( (BasicType) typeToUse ); } else { - throw new NotYetImplementedFor6Exception( getClass() ); - } - if ( binding == null || binding.getBindValue() == null ) { - // the user did not binding a value to the parameter being processed. This is the condition - // defined by `passNulls` and that value controls what happens here. If `passNulls` is - // {@code true} we will binding the NULL value into the statement; if `passNulls` is - // {@code false} we will not. - // - // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure - // parameter defines a default value. Deferring to that information would be the best option - if ( ( binding != null && binding.isBound() ) || isPassNullsEnabled() ) { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", - procedureCall.getProcedureName(), - this - ); - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED - && canDoNameParameterBinding( typeToUse, procedureCall ) ) { - //noinspection unchecked - ( (ProcedureParameterNamedBinder) typeToUse ).nullSafeSet( - statement, - null, - this.getName(), - procedureCall.getSession() + return new JdbcParameterImpl( (BasicType) typeToUse ) { + @Override + protected void bindParameterValue( + JdbcMapping jdbcMapping, + PreparedStatement statement, + Object bindValue, + int startPosition, + ExecutionContext executionContext) throws SQLException { + jdbcMapping.getJdbcValueBinder().bind( + (CallableStatement) statement, + bindValue, + name, + executionContext.getSession() ); } - else { - if ( position == null ) { - position = startIndex; - } - binder.bind( statement, null, position, procedureCall.getSession() ); - } - } - else { - throw new IllegalArgumentException( - "The parameter " + - ( name != null - ? "named [" + name + "]" - : "at position [" + position + "]" ) - + " was not set! You need to call the setParameter method." ); - } - } - else { - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED - && canDoNameParameterBinding( typeToUse, procedureCall ) ) { - //noinspection unchecked - ( (ProcedureParameterNamedBinder) typeToUse ).nullSafeSet( - statement, - binding.getBindValue(), - this.getName(), - procedureCall.getSession() - ); - } - else { - if ( position == null ) { - position = startIndex; - } - binder.bind( statement, binding.getBindValue(), position, procedureCall.getSession() ); - } + }; } } + else { + throw new NotYetImplementedFor6Exception( getClass() ); + } } private boolean canDoNameParameterBinding( @@ -269,4 +209,33 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme && hibernateType instanceof ProcedureParameterNamedBinder && ( (ProcedureParameterNamedBinder) hibernateType ).canDoSetting(); } + + @Override + public int hashCode() { + return Objects.hash( name, position, mode ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ProcedureParameterImpl that = (ProcedureParameterImpl) o; + return Objects.equals( name, that.name ) && + Objects.equals( position, that.position ) && + mode == that.mode; + } + + @Override + public String toString() { + if ( position == null ) { + return name; + } + else { + return position.toString(); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java index 36b3d877ff..f1bc685ff9 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java @@ -11,7 +11,9 @@ import java.util.List; import org.hibernate.QueryException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.procedure.spi.FunctionReturnImplementor; import org.hibernate.procedure.spi.ParameterStrategy; +import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.sql.exec.internal.JdbcCallImpl; @@ -42,39 +44,46 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt } @Override - public JdbcCall interpretCall( - String procedureName, - FunctionReturnImpl functionReturn, - ProcedureParameterMetadataImplementor parameterMetadata, - ProcedureParamBindings paramBindings, - SharedSessionContractImplementor session) { + public JdbcCall interpretCall(ProcedureCallImplementor procedureCall) { + final String procedureName = procedureCall.getProcedureName(); + final FunctionReturnImplementor functionReturn = procedureCall.getFunctionReturn(); + final ProcedureParameterMetadataImplementor parameterMetadata = procedureCall.getParameterMetadata(); + final SharedSessionContractImplementor session = procedureCall.getSession(); final List> registrations = parameterMetadata.getRegistrationsAsList(); - final StringBuilder buffer = new StringBuilder(9 + procedureName.length() + registrations.size() * 2).append( "{call " ) - .append( procedureName ) - .append( "(" ); + final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder( + parameterMetadata.hasNamedParameters() ? + ParameterStrategy.NAMED : + ParameterStrategy.POSITIONAL + ); + final StringBuilder buffer; + final int offset; + if ( functionReturn != null ) { + offset = 2; + buffer = new StringBuilder( 11 + procedureName.length() + registrations.size() * 2 ).append( "{?=call " ); + builder.setFunctionReturn( functionReturn.toJdbcFunctionReturn( session ) ); + } + else { + offset = 1; + buffer = new StringBuilder( 9 + procedureName.length() + registrations.size() * 2 ).append( "{call " ); + } + + buffer.append( procedureName ).append( "(" ); String sep = ""; for ( int i = 0; i < registrations.size(); i++ ) { - if ( registrations.get( i ).getMode() == ParameterMode.REF_CURSOR ) { + final ProcedureParameterImplementor parameter = registrations.get( i ); + if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { verifyRefCursorSupport( session.getJdbcServices().getJdbcEnvironment().getDialect() ); - buffer.append( sep ).append( "?" ); - sep = ","; - } - else { - buffer.append( sep ).append( "?" ); - sep = ","; } + buffer.append( sep ).append( "?" ); + sep = ","; + builder.addParameterRegistration( parameter.toJdbcParameterRegistration( i + offset, procedureCall ) ); } buffer.append( ")}" ); - return new JdbcCallImpl.Builder( - buffer.toString(), - parameterMetadata.hasNamedParameters() ? - ParameterStrategy.NAMED : - ParameterStrategy.POSITIONAL - ).buildJdbcCall(); - + builder.setCallableName( buffer.toString() ); + return builder.buildJdbcCall(); } private void verifyRefCursorSupport(Dialect dialect) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java index d408dbf10e..f21c8c484f 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java @@ -18,15 +18,12 @@ import org.hibernate.query.results.ResultSetMapping; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; -import org.jboss.logging.Logger; - /** * Utilities used to implement procedure call support. * * @author Steve Ebersole */ public class Util { - private static final Logger log = Logger.getLogger( Util.class ); private Util() { } @@ -85,8 +82,7 @@ public class Util { } } else { - final JavaType basicType = javaTypeRegistry.getDescriptor( - resultSetMappingClass ); + final JavaType basicType = javaTypeRegistry.getDescriptor( resultSetMappingClass ); if ( basicType != null ) { resultSetMapping.addResultBuilder( new ScalarDomainResultBuilder<>( basicType ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java index 0c14378ce4..87c21bfb59 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java @@ -9,8 +9,6 @@ package org.hibernate.procedure.spi; import java.sql.CallableStatement; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.procedure.internal.FunctionReturnImpl; -import org.hibernate.procedure.internal.ProcedureParamBindings; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.sql.exec.spi.JdbcCall; @@ -18,16 +16,11 @@ import org.hibernate.sql.exec.spi.JdbcCall; * @author Steve Ebersole */ public interface CallableStatementSupport { - JdbcCall interpretCall( - String procedureName, - FunctionReturnImpl functionReturn, - ProcedureParameterMetadataImplementor parameterMetadata, - ProcedureParamBindings paramBindings, - SharedSessionContractImplementor session); + JdbcCall interpretCall(ProcedureCallImplementor procedureCall); void registerParameters( String procedureName, - ProcedureCallImplementor procedureCall, + JdbcCall procedureCall, CallableStatement statement, ParameterStrategy parameterStrategy, ProcedureParameterMetadataImplementor parameterMetadata, diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java index b31e9f2112..5a271ad0cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java @@ -6,10 +6,21 @@ */ package org.hibernate.procedure.spi; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.FunctionReturn; +import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; /** * @author Steve Ebersole */ -public interface FunctionReturnImplementor extends FunctionReturn, ProcedureParameterImplementor { +public interface FunctionReturnImplementor extends FunctionReturn, ProcedureParameterImplementor { + @Override + default JdbcCallParameterRegistration toJdbcParameterRegistration( + int startIndex, + ProcedureCallImplementor procedureCall) { + return toJdbcFunctionReturn( procedureCall.getSession() ); + } + + JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor session); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java index 04ac900f8b..19806484ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java @@ -9,15 +9,17 @@ package org.hibernate.procedure.spi; import java.util.Calendar; import java.util.Date; import java.util.List; + +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.type.BasicTypeReference; + import jakarta.persistence.FlushModeType; import jakarta.persistence.Parameter; import jakarta.persistence.ParameterMode; import jakarta.persistence.TemporalType; -import org.hibernate.procedure.ProcedureCall; -import org.hibernate.query.spi.QueryImplementor; -import org.hibernate.type.BasicTypeReference; - /** * @author Steve Ebersole */ @@ -29,6 +31,11 @@ public interface ProcedureCallImplementor extends ProcedureCall, QueryImpleme ParameterStrategy getParameterStrategy(); + FunctionReturnImplementor getFunctionReturn(); + + @Override + ProcedureParameterMetadataImplementor getParameterMetadata(); + @Override default R getSingleResult() { return uniqueResult(); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java index 0087bd6ad6..87fb3dbc54 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java @@ -6,12 +6,10 @@ */ package org.hibernate.procedure.spi; -import java.sql.CallableStatement; -import java.sql.SQLException; - import org.hibernate.Incubating; import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; /** * SPI extension for ProcedureParameter @@ -20,14 +18,7 @@ import org.hibernate.query.spi.QueryParameterImplementor; */ @Incubating public interface ProcedureParameterImplementor extends ProcedureParameter, QueryParameterImplementor { - /** - * Allow the parameter to register itself with the JDBC CallableStatement, - * if necessary, as well as perform any other needed preparation for exeuction - * - * @throws SQLException Indicates a problem with any underlying JDBC calls - */ - void prepare( - CallableStatement statement, - int startIndex, - ProcedureCallImplementor callImplementor) throws SQLException; + + JdbcCallParameterRegistration toJdbcParameterRegistration(int startIndex, ProcedureCallImplementor procedureCall); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java index 5b6c7040bf..82fe2fef29 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/ProcedureParameter.java @@ -6,11 +6,11 @@ */ package org.hibernate.query.procedure; -import jakarta.persistence.ParameterMode; - import org.hibernate.Incubating; import org.hibernate.query.QueryParameter; +import jakarta.persistence.ParameterMode; + /** * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM * @@ -26,32 +26,4 @@ public interface ProcedureParameter extends QueryParameter { */ ParameterMode getMode(); - /** - * How will an unbound value be handled in terms of the JDBC parameter? - * - * @return {@code true} here indicates that NULL should be passed; {@code false} indicates - * that it is ignored. - * - * @deprecated (since 6.0) : Passing null or not is now triggered by whether - * setting the parameter was called at all. In other words a distinction is - * made between calling `setParameter` passing {@code null} versus not calling - * `setParameter` at all. In the first case, we pass along the {@code null}; in - * the second we do not pass {@code null}. - */ - @Deprecated - default boolean isPassNullsEnabled() { - return false; - } - - /** - * Controls how unbound values for this IN/INOUT parameter registration will be handled prior to - * execution. - * - * @param enabled {@code true} indicates that the NULL should be passed; {@code false} indicates it should not. - * - * @deprecated (since 6.0) : see {@link #isPassNullsEnabled} - */ - @Deprecated - default void enablePassingNulls(boolean enabled) { - } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java index cb094b5eaf..d7b686465d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java @@ -9,7 +9,6 @@ package org.hibernate.sql.exec.internal; import java.sql.PreparedStatement; import java.sql.SQLException; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.mapping.IndexedConsumer; import org.hibernate.metamodel.mapping.JdbcMapping; @@ -25,9 +24,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -80,6 +77,7 @@ public abstract class AbstractJdbcParameter throw new ExecutionException( "JDBC parameter value not bound - " + this ); } + final Object bindValue = binding.getBindValue(); JdbcMapping jdbcMapping = binding.getBindType(); if ( jdbcMapping == null ) { @@ -88,11 +86,18 @@ public abstract class AbstractJdbcParameter // If the parameter type is not known from the context i.e. null or Object, infer it from the bind value if ( jdbcMapping == null || jdbcMapping.getMappedJavaTypeDescriptor().getJavaTypeClass() == Object.class ) { - jdbcMapping = guessBindType( executionContext, binding, jdbcMapping ); + jdbcMapping = guessBindType( executionContext, bindValue, jdbcMapping ); } - final Object bindValue = binding.getBindValue(); + bindParameterValue( jdbcMapping, statement, bindValue, startPosition, executionContext ); + } + protected void bindParameterValue( + JdbcMapping jdbcMapping, + PreparedStatement statement, + Object bindValue, + int startPosition, + ExecutionContext executionContext) throws SQLException { //noinspection unchecked jdbcMapping.getJdbcValueBinder().bind( statement, @@ -102,13 +107,14 @@ public abstract class AbstractJdbcParameter ); } - private JdbcMapping guessBindType(ExecutionContext executionContext, JdbcParameterBinding binding, JdbcMapping jdbcMapping) { - if ( binding.getBindValue() == null && jdbcMapping != null ) { + private JdbcMapping guessBindType(ExecutionContext executionContext, Object bindValue, JdbcMapping jdbcMapping) { + if ( bindValue == null && jdbcMapping != null ) { return jdbcMapping; } - final AllowableParameterType parameterType = executionContext.getSession().getFactory() - .resolveParameterBindType( binding.getBindValue() ); + final AllowableParameterType parameterType = executionContext.getSession() + .getFactory() + .resolveParameterBindType( bindValue ); if ( parameterType instanceof JdbcMapping ) { return (JdbcMapping) parameterType; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallFunctionReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallFunctionReturnImpl.java index 40b08a51db..018718b398 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallFunctionReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallFunctionReturnImpl.java @@ -6,25 +6,23 @@ */ package org.hibernate.sql.exec.internal; -import jakarta.persistence.ParameterMode; - import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn; +import jakarta.persistence.ParameterMode; + /** * @author Steve Ebersole */ public class JdbcCallFunctionReturnImpl extends JdbcCallParameterRegistrationImpl implements JdbcCallFunctionReturn { public JdbcCallFunctionReturnImpl( - int jdbcTypeCode, AllowableParameterType ormType, JdbcCallParameterExtractorImpl parameterExtractor, JdbcCallRefCursorExtractorImpl refCursorExtractor) { super( null, - 0, - ParameterMode.OUT, - jdbcTypeCode, + 1, + ParameterMode.REF_CURSOR, ormType, null, parameterExtractor, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallImpl.java index c2947df744..a59ca22060 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallImpl.java @@ -115,9 +115,9 @@ public class JdbcCallImpl implements JdbcCall { } public static class Builder { - private final String callableName; private final ParameterStrategy parameterStrategy; + private String callableName; private JdbcCallFunctionReturn functionReturn; private List parameterRegistrations; @@ -125,8 +125,7 @@ public class JdbcCallImpl implements JdbcCall { private List parameterExtractors; private List refCursorExtractors; - public Builder(String callableName, ParameterStrategy parameterStrategy) { - this.callableName = callableName; + public Builder(ParameterStrategy parameterStrategy) { this.parameterStrategy = parameterStrategy; } @@ -134,6 +133,10 @@ public class JdbcCallImpl implements JdbcCall { return new JdbcCallImpl( this ); } + public void setCallableName(String callableName) { + this.callableName = callableName; + } + public void setFunctionReturn(JdbcCallFunctionReturn functionReturn) { this.functionReturn = functionReturn; } @@ -149,6 +152,7 @@ public class JdbcCallImpl implements JdbcCall { switch ( registration.getParameterMode() ) { case REF_CURSOR: { + addParameterBinder( JdbcParameterBinder.NOOP ); addRefCursorExtractor( registration.getRefCursorExtractor() ); break; } @@ -162,6 +166,7 @@ public class JdbcCallImpl implements JdbcCall { break; } case OUT: { + addParameterBinder( JdbcParameterBinder.NOOP ); addParameterExtractor( registration.getParameterExtractor() ); break; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java index e4d9be76af..827d6e3b76 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java @@ -10,9 +10,9 @@ import java.sql.CallableStatement; import java.sql.SQLException; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.BasicDomainType; -import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor; /** @@ -20,7 +20,7 @@ import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor; * * @author Steve Ebersole */ -public class JdbcCallParameterExtractorImpl implements JdbcCallParameterExtractor { +public class JdbcCallParameterExtractorImpl implements JdbcCallParameterExtractor { private final String callableName; private final String parameterName; private final int parameterPosition; @@ -58,7 +58,7 @@ public class JdbcCallParameterExtractorImpl implements JdbcCallParameterExtra public T extractValue( CallableStatement callableStatement, boolean shouldUseJdbcNamedParameters, - ExecutionContext executionContext) { + SharedSessionContractImplementor session) { final boolean useNamed = shouldUseJdbcNamedParameters && parameterName != null; @@ -68,14 +68,14 @@ public class JdbcCallParameterExtractorImpl implements JdbcCallParameterExtra try { if ( useNamed ) { - return (T) ormType.extract( callableStatement, parameterName, executionContext.getSession() ); + return (T) ormType.extract( callableStatement, parameterName, session ); } else { - return (T) ormType.extract( callableStatement, parameterPosition, executionContext.getSession() ); + return (T) ormType.extract( callableStatement, parameterPosition, session ); } } catch (SQLException e) { - throw executionContext.getSession().getJdbcServices().getSqlExceptionHelper().convert( + throw session.getJdbcServices().getSqlExceptionHelper().convert( e, "Unable to extract OUT/INOUT parameter value" ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java index 87604fcae9..13b32b114b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java @@ -8,7 +8,6 @@ package org.hibernate.sql.exec.internal; import java.sql.CallableStatement; import java.sql.SQLException; -import jakarta.persistence.ParameterMode; import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -19,6 +18,8 @@ import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.type.descriptor.jdbc.JdbcType; +import jakarta.persistence.ParameterMode; + /** * @author Steve Ebersole */ @@ -26,7 +27,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis private final String name; private final int jdbcParameterPositionStart; private final ParameterMode parameterMode; - private final int jdbcTypeCode; private final AllowableParameterType ormType; private final JdbcParameterBinder parameterBinder; private final JdbcCallParameterExtractorImpl parameterExtractor; @@ -36,7 +36,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis String name, int jdbcParameterPositionStart, ParameterMode parameterMode, - int jdbcTypeCode, AllowableParameterType ormType, JdbcParameterBinder parameterBinder, JdbcCallParameterExtractorImpl parameterExtractor, @@ -44,7 +43,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis this.name = name; this.jdbcParameterPositionStart = jdbcParameterPositionStart; this.parameterMode = parameterMode; - this.jdbcTypeCode = jdbcTypeCode; this.ormType = ormType; this.parameterBinder = parameterBinder; this.parameterExtractor = parameterExtractor; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java index f75c452334..5c64b11970 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java @@ -31,7 +31,6 @@ public class JdbcCallRefCursorExtractorImpl implements JdbcCallRefCursorExtracto this.jdbcParameterPosition = jdbcParameterPosition; } - @Override public ResultSet extractResultSet( CallableStatement callableStatement, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCallParameterExtractor.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCallParameterExtractor.java index 6b5d8aff54..947fc1ca89 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCallParameterExtractor.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCallParameterExtractor.java @@ -9,6 +9,7 @@ package org.hibernate.sql.exec.spi; import java.sql.CallableStatement; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; /** @@ -25,5 +26,5 @@ public interface JdbcCallParameterExtractor { T extractValue( CallableStatement callableStatement, boolean shouldUseJdbcNamedParameters, - ExecutionContext executionContext); + SharedSessionContractImplementor session); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBinder.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBinder.java index c1a777b0df..4bf1ea859b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBinder.java @@ -16,6 +16,9 @@ import java.sql.SQLException; * @author John O'Hara */ public interface JdbcParameterBinder { + + JdbcParameterBinder NOOP = (statement, startPosition, jdbcParameterBindings, executionContext) -> {}; + /** * Bind the appropriate value in the JDBC statement */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java index 4ccbd7d64a..5c510cd31d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerStandard.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.jdbc.internal; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.sql.ast.spi.SqlSelection; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/MySQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/MySQLStoredProcedureTest.java index 6fd55b5b0b..0e7cfdea05 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/MySQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/MySQLStoredProcedureTest.java @@ -357,7 +357,7 @@ public class MySQLStoredProcedureTest { scope.inTransaction( entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); - procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); procedureCall.setParameter( 1, null ); @@ -369,7 +369,7 @@ public class MySQLStoredProcedureTest { scope.inTransaction( entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); - procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); procedureCall.setParameter( 1, "test" ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java index c091cee962..8e55573825 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java @@ -405,6 +405,23 @@ public class OracleStoredProcedureTest { ); } + @Test + public void testNamedProcedureCallStoredProcedureRefCursor(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + List postAndComments = entityManager + .createNamedStoredProcedureQuery( + "fn_person_and_phones_sq" ) + .setParameter( 1, 1L ) + .getResultList(); + Object[] postAndComment = postAndComments.get( 0 ); + Person person = (Person) postAndComment[0]; + Phone phone = (Phone) postAndComment[1]; + assertEquals( 2, postAndComments.size() ); + } + ); + } + @Test public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC(EntityManagerFactoryScope scope) { scope.inTransaction( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java index 6a77f5b6c9..b91b6d3c01 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java @@ -366,7 +366,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe doInJPA( this::entityManagerFactory, entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); - procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); procedureCall.setParameter( 1, null ); @@ -378,7 +378,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe doInJPA( this::entityManagerFactory, entityManager -> { ProcedureCall procedureCall = entityManager.unwrap( Session.class ) .createStoredProcedureCall( "sp_is_null" ); - procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); procedureCall.registerParameter( 2, Boolean.class, ParameterMode.OUT ); procedureCall.setParameter( 1, "test" ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoredProcedureParameterTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoredProcedureParameterTypeTest.java index e24d5f98eb..2abf4d3271 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoredProcedureParameterTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoredProcedureParameterTypeTest.java @@ -407,7 +407,7 @@ public class StoredProcedureParameterTypeTest { scope.inTransaction( session -> { ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); - procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ).enablePassingNulls( true ); + procedureCall.registerParameter( 1, StandardBasicTypes.STRING, ParameterMode.IN ); procedureCall.setParameter( 1, null ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/NumValue.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/NumValue.java index 5d2be4c200..40890d2169 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/NumValue.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/NumValue.java @@ -14,6 +14,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.NamedStoredProcedureQuery; import jakarta.persistence.ParameterMode; +import jakarta.persistence.QueryHint; import jakarta.persistence.StoredProcedureParameter; import jakarta.persistence.Table; @@ -23,9 +24,7 @@ import jakarta.persistence.Table; name = "NumValue.getSomeValues", procedureName = "f_test_return_cursor", resultClasses = NumValue.class, - parameters = { - @StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = ResultSet.class) - } + hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true") ) public class NumValue implements Serializable { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/test/procedure/Person.java b/hibernate-core/src/test/java/org/hibernate/test/procedure/Person.java index 6fcfcfb4e0..164f026dfc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/procedure/Person.java +++ b/hibernate-core/src/test/java/org/hibernate/test/procedure/Person.java @@ -16,15 +16,19 @@ import jakarta.persistence.FieldResult; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.NamedStoredProcedureQueries; +import jakarta.persistence.NamedStoredProcedureQuery; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderColumn; +import jakarta.persistence.QueryHint; import jakarta.persistence.SqlResultSetMapping; import jakarta.persistence.SqlResultSetMappings; +import jakarta.persistence.StoredProcedureParameter; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import jakarta.persistence.Version; import org.hibernate.annotations.NamedNativeQuery; +import org.hibernate.annotations.QueryHints; /** * @author Vlad Mihalcea @@ -41,6 +45,15 @@ import org.hibernate.annotations.NamedNativeQuery; callable = false, resultSetMapping = "person_with_phones_hana" ) +@NamedStoredProcedureQuery( + name = "fn_person_and_phones_sq", + procedureName = "fn_person_and_phones", + resultSetMappings = "person_with_phones", + parameters = { + @StoredProcedureParameter(type = Long.class) + }, + hints = @QueryHint(name = QueryHints.CALLABLE_FUNCTION, value = "true") +) @SqlResultSetMappings({ @SqlResultSetMapping( name = "person_with_phones", diff --git a/migration-guide.adoc b/migration-guide.adoc index 03a210c824..98e6eac82d 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -140,7 +140,26 @@ List postAndComments = entityManager.createNamedQuery("fn_person_and_p is going to throw an `IllegalArgumentException`. -The migration code is +If you want to retain the named version, you can change the definition to + +``` +@NamedStoredProcedureQuery( + name = "fn_person_and_phones", + procedureName = "fn_person_and_phones", + resultSetMapping = "person_with_phones", + hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true"), + parameters = { + @StoredProcedureParameter(type = Long.class) + } +) +``` + +and call this like +``` +List postAndComments = entityManager.createNamedStoredProcedureQuery( "fn_person_and_phones" ).setParameter( 1, 1L ).getResultList(); +``` + +or not define the stored procedure and use this code ``` List postAndComments = entityManager.createStoredProcedureQuery( "fn_person_and_phones", "person_with_phones" ).setParameter( 1, 1L ).getResultList(); ```