Cleanup stored procedure handling and add support for stored procedure function return

This commit is contained in:
Christian Beikov 2021-12-24 00:21:48 +01:00
parent 1aefd1977a
commit fb8186d3e8
30 changed files with 573 additions and 499 deletions

View File

@ -43,6 +43,11 @@ public class QueryHints {
*/ */
public static final String CACHEABLE = "org.hibernate.cacheable"; 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. * Defines a comment to be applied to the SQL sent to the database.
* *

View File

@ -7,57 +7,29 @@
package org.hibernate.procedure.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement; 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.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.procedure.spi.ParameterStrategy; 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.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.spi.JdbcCall;
import jakarta.persistence.ParameterMode; import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
public abstract class AbstractStandardCallableStatementSupport implements CallableStatementSupport { public abstract class AbstractStandardCallableStatementSupport implements CallableStatementSupport {
@Override @Override
public void registerParameters( public void registerParameters(
String procedureName, String procedureName,
ProcedureCallImplementor procedureCall, JdbcCall procedureCall,
CallableStatement statement, CallableStatement statement,
ParameterStrategy parameterStrategy, ParameterStrategy parameterStrategy,
ProcedureParameterMetadataImplementor parameterMetadata, ProcedureParameterMetadataImplementor parameterMetadata,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
if ( procedureCall.getFunctionReturn() != null ) {
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList(); procedureCall.getFunctionReturn().registerParameter( statement, session );
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 );
}
}
} }
catch (SQLException e) { for ( JdbcCallParameterRegistration parameterRegistration : procedureCall.getParameterRegistrations() ) {
throw session.getJdbcServices().getSqlExceptionHelper().convert( parameterRegistration.registerParameter( statement, session );
e,
"Error registering CallableStatement parameters",
procedureName
);
} }
} }
} }

View File

@ -7,10 +7,7 @@
package org.hibernate.procedure.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import jakarta.persistence.ParameterMode;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor; 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.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.ParameterMode;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class FunctionReturnImpl implements FunctionReturnImplementor { public class FunctionReturnImpl<T> implements FunctionReturnImplementor<T> {
private final ProcedureCallImplementor procedureCall; private final ProcedureCallImplementor<T> procedureCall;
private int jdbcTypeCode; private final int jdbcTypeCode;
private AllowableOutputParameterType<?> ormType; private AllowableOutputParameterType<T> ormType;
public FunctionReturnImpl(ProcedureCallImplementor procedureCall, int jdbcTypeCode) { public FunctionReturnImpl(ProcedureCallImplementor<T> procedureCall, int jdbcTypeCode) {
this.procedureCall = procedureCall; this.procedureCall = procedureCall;
this.jdbcTypeCode = jdbcTypeCode; this.jdbcTypeCode = jdbcTypeCode;
} }
public FunctionReturnImpl(ProcedureCallImplementor procedureCall, AllowableOutputParameterType ormType) { public FunctionReturnImpl(ProcedureCallImplementor<T> procedureCall, AllowableOutputParameterType<T> ormType) {
this.procedureCall = procedureCall; this.procedureCall = procedureCall;
this.jdbcTypeCode = ormType.getJdbcTypeDescriptor().getJdbcTypeCode(); this.jdbcTypeCode = ormType.getJdbcTypeDescriptor().getJdbcTypeCode();
this.ormType = ormType; this.ormType = ormType;
} }
@Override
public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) { public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) {
final AllowableParameterType ormType; final AllowableParameterType<T> ormType;
final JdbcCallRefCursorExtractorImpl refCursorExtractor; final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl parameterExtractor; final JdbcCallParameterExtractorImpl<T> parameterExtractor;
if ( getJdbcTypeCode() == Types.REF_CURSOR ) { if ( getJdbcTypeCode() == Types.REF_CURSOR ) {
refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 0 ); refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 1 );
ormType = null; ormType = null;
parameterExtractor = null; parameterExtractor = null;
} }
else { else {
final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getMetamodel().getTypeConfiguration(); final TypeConfiguration typeConfiguration = persistenceContext.getFactory().getMetamodel().getTypeConfiguration();
final JdbcType sqlTypeDescriptor = typeConfiguration.getJdbcTypeDescriptorRegistry() final JdbcType sqlTypeDescriptor = typeConfiguration.getJdbcTypeDescriptorRegistry()
.getDescriptor( getJdbcTypeCode() ); .getDescriptor( getJdbcTypeCode() );
final BasicJavaType<?> javaTypeMapping = sqlTypeDescriptor final BasicJavaType<?> javaTypeMapping = sqlTypeDescriptor
.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration ); .getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
ormType = typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaTypeClass() ); //noinspection unchecked
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), null, 0, ormType ); ormType = (AllowableParameterType<T>) typeConfiguration.standardBasicTypeForJavaType( javaTypeMapping.getJavaTypeClass() );
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), null, 1, ormType );
refCursorExtractor = null; refCursorExtractor = null;
} }
return new JdbcCallFunctionReturnImpl( getJdbcTypeCode(), ormType, parameterExtractor, refCursorExtractor ); return new JdbcCallFunctionReturnImpl( ormType, parameterExtractor, refCursorExtractor );
} }
@Override @Override
@ -80,7 +78,7 @@ public class FunctionReturnImpl implements FunctionReturnImplementor {
} }
@Override @Override
public AllowableParameterType getHibernateType() { public AllowableParameterType<T> getHibernateType() {
return ormType; return ormType;
} }
@ -91,7 +89,7 @@ public class FunctionReturnImpl implements FunctionReturnImplementor {
@Override @Override
public Integer getPosition() { public Integer getPosition() {
return 0; return 1;
} }
@Override @Override
@ -125,17 +123,11 @@ public class FunctionReturnImpl implements FunctionReturnImplementor {
public NamedCallableQueryMemento.ParameterMemento toMemento() { public NamedCallableQueryMemento.ParameterMemento toMemento() {
return session -> { return session -> {
if ( ormType != null ) { if ( ormType != null ) {
return new FunctionReturnImpl( procedureCall, ormType ); return new FunctionReturnImpl<>( procedureCall, ormType );
} }
else { 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() );
}
} }

View File

@ -10,7 +10,9 @@ import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.FunctionReturnImplementor;
import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.internal.JdbcCallImpl; import org.hibernate.sql.exec.internal.JdbcCallImpl;
@ -28,12 +30,11 @@ public class PostgresCallableStatementSupport extends AbstractStandardCallableSt
public static final PostgresCallableStatementSupport INSTANCE = new PostgresCallableStatementSupport(); public static final PostgresCallableStatementSupport INSTANCE = new PostgresCallableStatementSupport();
@Override @Override
public JdbcCall interpretCall( public JdbcCall interpretCall(ProcedureCallImplementor<?> procedureCall) {
String procedureName, final String procedureName = procedureCall.getProcedureName();
FunctionReturnImpl functionReturn, final FunctionReturnImplementor functionReturn = procedureCall.getFunctionReturn();
ProcedureParameterMetadataImplementor parameterMetadata, final ProcedureParameterMetadataImplementor parameterMetadata = procedureCall.getParameterMetadata();
ProcedureParamBindings paramBindings, final SharedSessionContractImplementor session = procedureCall.getSession();
SharedSessionContractImplementor session) {
final boolean firstParamIsRefCursor = parameterMetadata.getParameterCount() != 0 final boolean firstParamIsRefCursor = parameterMetadata.getParameterCount() != 0
&& isFirstParameterModeRefCursor( parameterMetadata ); && isFirstParameterModeRefCursor( parameterMetadata );
@ -45,42 +46,50 @@ public class PostgresCallableStatementSupport extends AbstractStandardCallableSt
} }
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList(); final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder(
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
);
final StringBuilder buffer; final StringBuilder buffer;
if ( firstParamIsRefCursor ) { final int offset;
buffer = new StringBuilder(11 + procedureName.length() + registrations.size() * 2).append( "{?=call " ); 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 { 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( "(" ); 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 = ""; String sep = "";
for ( int i = startIndex; i < registrations.size(); i++ ) { 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( throw new HibernateException(
"PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" ); "PostgreSQL supports only one REF_CURSOR parameter, but multiple were registered" );
} }
buffer.append( sep ).append( "?" ); buffer.append( sep ).append( "?" );
sep = ","; sep = ",";
builder.addParameterRegistration( parameter.toJdbcParameterRegistration( i + offset, procedureCall ) );
} }
buffer.append( ")}" ); buffer.append( ")}" );
return new JdbcCallImpl.Builder( builder.setCallableName( buffer.toString() );
buffer.toString(), return builder.buildJdbcCall();
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
).buildJdbcCall();
} }
private static boolean isFirstParameterModeRefCursor(ProcedureParameterMetadataImplementor parameterMetadata) { private static boolean isFirstParameterModeRefCursor(ProcedureParameterMetadataImplementor parameterMetadata) {

View File

@ -7,15 +7,83 @@
package org.hibernate.procedure.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; 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.FlushModeType;
import jakarta.persistence.LockModeType; import jakarta.persistence.LockModeType;
import jakarta.persistence.NoResultException; import jakarta.persistence.NoResultException;
@ -26,50 +94,6 @@ import jakarta.persistence.PersistenceException;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import jakarta.persistence.TransactionRequiredException; 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} * Standard implementation of {@link ProcedureCall}
* *
@ -82,7 +106,7 @@ public class ProcedureCallImpl<R>
private final String procedureName; private final String procedureName;
private FunctionReturnImpl functionReturn; private FunctionReturnImpl<R> functionReturn;
private final ProcedureParameterMetadataImpl parameterMetadata; private final ProcedureParameterMetadataImpl parameterMetadata;
private final ProcedureParamBindings paramBindings; private final ProcedureParamBindings paramBindings;
@ -93,6 +117,7 @@ public class ProcedureCallImpl<R>
private final QueryOptionsImpl queryOptions = new QueryOptionsImpl(); private final QueryOptionsImpl queryOptions = new QueryOptionsImpl();
private JdbcCall call;
private ProcedureOutputsImpl outputs; private ProcedureOutputsImpl outputs;
@ -263,6 +288,30 @@ public class ProcedureCallImpl<R>
synchronizedQuerySpaces::add, synchronizedQuerySpaces::add,
() -> getSession().getFactory() () -> 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<Class<?>> 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 @Override
@ -299,9 +348,14 @@ public class ProcedureCallImpl<R>
return functionReturn != null; return functionReturn != null;
} }
@Override
public FunctionReturnImplementor<R> getFunctionReturn() {
return functionReturn;
}
@Override @Override
public ProcedureCall markAsFunctionCall(int sqlType) { public ProcedureCall markAsFunctionCall(int sqlType) {
functionReturn = new FunctionReturnImpl( this, sqlType ); functionReturn = new FunctionReturnImpl<>( this, sqlType );
return this; return this;
} }
@ -519,15 +573,27 @@ public class ProcedureCallImpl<R>
.getJdbcEnvironment() .getJdbcEnvironment()
.getDialect() .getDialect()
.getCallableStatementSupport(); .getCallableStatementSupport();
final ProcedureParameterMetadataImpl parameterMetadata = getParameterMetadata(); this.call = callableStatementSupport.interpretCall( this );
final JdbcCall call = callableStatementSupport.interpretCall(
procedureName,
functionReturn,
parameterMetadata,
paramBindings,
getSession()
);
final Map<ProcedureParameter<?>, JdbcCallParameterRegistration> parameterRegistrations = new IdentityHashMap<>();
final List<JdbcCallRefCursorExtractor> 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<? extends ProcedureParameterImplementor<?>> registrations = getParameterMetadata().getRegistrationsAsList();
final List<JdbcCallParameterRegistration> 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 ); LOG.debugf( "Preparing procedure call : %s", call );
final CallableStatement statement = (CallableStatement) getSession() final CallableStatement statement = (CallableStatement) getSession()
@ -535,35 +601,100 @@ public class ProcedureCallImpl<R>
.getStatementPreparer() .getStatementPreparer()
.prepareStatement( call.getSql(), true ); .prepareStatement( call.getSql(), true );
callableStatementSupport.registerParameters( procedureName, this,statement, parameterMetadata.getParameterStrategy(), parameterMetadata, getSession() ); // Register the parameter mode and type
// getParameterMetadata().visitRegistrations( callableStatementSupport.registerParameters(
// new Consumer<QueryParameter<?>>() { procedureName,
// int i = 1; call,
// statement,
// @Override parameterMetadata.getParameterStrategy(),
// public void accept(QueryParameter queryParameter) { parameterMetadata,
// try { getSession()
// 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()
// );
// }
// }
// }
// );
return new ProcedureOutputsImpl( this, statement ); // Apply the parameter bindings
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( parameterRegistrations.size() );
for ( Map.Entry<ProcedureParameter<?>, 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 @Override
@ -618,25 +749,6 @@ public class ProcedureCallImpl<R>
return this; 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<ProcedureParameterImplementor> 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 @Override
public NamedCallableQueryMemento toMemento(String name) { public NamedCallableQueryMemento toMemento(String name) {
return new NamedCallableQueryMementoImpl( return new NamedCallableQueryMementoImpl(

View File

@ -8,19 +8,16 @@ package org.hibernate.procedure.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.ResultSet; 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.ParameterMisuseException;
import org.hibernate.procedure.ProcedureOutputs; 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.query.procedure.ProcedureParameter;
import org.hibernate.result.Output; import org.hibernate.result.Output;
import org.hibernate.result.internal.OutputsImpl; import org.hibernate.result.internal.OutputsImpl;
import org.hibernate.sql.exec.ExecutionException; import org.hibernate.sql.exec.ExecutionException;
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor;
import jakarta.persistence.ParameterMode; import jakarta.persistence.ParameterMode;
@ -33,60 +30,51 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput
private final ProcedureCallImpl procedureCall; private final ProcedureCallImpl procedureCall;
private final CallableStatement callableStatement; private final CallableStatement callableStatement;
private final ProcedureParameterImplementor[] refCursorParameters; private final Map<ProcedureParameter<?>, JdbcCallParameterRegistration> parameterRegistrations;
private final JdbcCallRefCursorExtractor[] refCursorParameters;
private int refCursorParamIndex; private int refCursorParamIndex;
ProcedureOutputsImpl(ProcedureCallImpl procedureCall, CallableStatement callableStatement) { ProcedureOutputsImpl(
ProcedureCallImpl procedureCall,
Map<ProcedureParameter<?>, JdbcCallParameterRegistration> parameterRegistrations,
JdbcCallRefCursorExtractor[] refCursorParameters,
CallableStatement callableStatement) {
super( procedureCall, callableStatement ); super( procedureCall, callableStatement );
this.procedureCall = procedureCall; this.procedureCall = procedureCall;
this.callableStatement = callableStatement; this.callableStatement = callableStatement;
this.parameterRegistrations = parameterRegistrations;
this.refCursorParameters = procedureCall.collectRefCursorParameters(); this.refCursorParameters = refCursorParameters;
} }
@Override @Override
public <T> T getOutputParameterValue(ProcedureParameter<T> parameter) { public <T> T getOutputParameterValue(ProcedureParameter<T> parameter) {
final AllowableParameterType<T> hibernateType = parameter.getHibernateType();
if ( parameter.getMode() == ParameterMode.IN ) { if ( parameter.getMode() == ParameterMode.IN ) {
throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); 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 { try {
if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { if ( registration.getParameterMode() == 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<?> ) {
//noinspection unchecked //noinspection unchecked
if ( parameter.getPosition() != null ) { return (T) registration.getRefCursorExtractor().extractResultSet(
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract( callableStatement,
callableStatement, procedureCall.getSession()
parameter.getPosition(), );
procedureCall.getSession()
);
}
else {
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract(
callableStatement,
parameter.getName(),
procedureCall.getSession()
);
}
} }
else { 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( throw new ExecutionException(
"Error extracting procedure output parameter value [" "Error extracting procedure output parameter value [" + parameter + "]",
+ parameter.getPosition() != null ?
String.valueOf( parameter.getPosition() ) :
parameter.getName() + "]",
e e
); );
} }
@ -118,7 +106,7 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput
@Override @Override
public boolean indicatesMoreOutputs() { public boolean indicatesMoreOutputs() {
return super.indicatesMoreOutputs() return super.indicatesMoreOutputs()
|| ProcedureOutputsImpl.this.refCursorParamIndex < ProcedureOutputsImpl.this.refCursorParameters.length; || ProcedureOutputsImpl.this.refCursorParamIndex < refCursorParameters.length;
} }
@Override @Override
@ -128,19 +116,8 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput
@Override @Override
protected Output buildExtendedReturn() { protected Output buildExtendedReturn() {
ProcedureOutputsImpl.this.refCursorParamIndex++; final JdbcCallRefCursorExtractor refCursorParam = refCursorParameters[ProcedureOutputsImpl.this.refCursorParamIndex++];
final ProcedureParameterImplementor refCursorParam = ProcedureOutputsImpl.this.refCursorParameters[refCursorParamIndex]; final ResultSet resultSet = refCursorParam.extractResultSet( callableStatement, procedureCall.getSession() );
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() );
}
return buildResultSetOutput( () -> extractResults( resultSet ) ); return buildResultSetOutput( () -> extractResults( resultSet ) );
} }
} }

View File

@ -7,13 +7,14 @@
package org.hibernate.procedure.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Objects; import java.util.Objects;
import jakarta.persistence.ParameterMode;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.NamedCallableQueryMemento;
import org.hibernate.procedure.spi.ParameterStrategy; 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.AbstractQueryParameter;
import org.hibernate.query.internal.BindingTypeHelper; import org.hibernate.query.internal.BindingTypeHelper;
import org.hibernate.query.spi.QueryParameterBinding; 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.BasicType;
import org.hibernate.type.ProcedureParameterNamedBinder; 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.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger; import jakarta.persistence.ParameterMode;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> implements ProcedureParameterImplementor<T> { public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> implements ProcedureParameterImplementor<T> {
private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class );
private final String name; private final String name;
private Integer position; private final Integer position;
private final ParameterMode mode; private final ParameterMode mode;
private final Class<T> javaType; private final Class<T> javaType;
public ProcedureParameterImpl( public ProcedureParameterImpl(
String name, String name,
ParameterMode mode, ParameterMode mode,
@ -111,29 +114,9 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
} }
@Override @Override
public boolean equals(Object o) { public JdbcCallParameterRegistration toJdbcParameterRegistration(
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,
int startIndex, int startIndex,
ProcedureCallImplementor<?> procedureCall) throws SQLException { ProcedureCallImplementor<?> procedureCall) {
final QueryParameterBinding<T> binding = procedureCall.getParameterBindings().getBinding( this ); final QueryParameterBinding<T> binding = procedureCall.getParameterBindings().getBinding( this );
final TypeConfiguration typeConfiguration = procedureCall.getSession().getFactory().getTypeConfiguration(); final TypeConfiguration typeConfiguration = procedureCall.getSession().getFactory().getTypeConfiguration();
final AllowableParameterType<T> typeToUse = BindingTypeHelper.INSTANCE.resolveTemporalPrecision( final AllowableParameterType<T> typeToUse = BindingTypeHelper.INSTANCE.resolveTemporalPrecision(
@ -144,115 +127,72 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
typeConfiguration typeConfiguration
); );
if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { final String name;
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
// if ( sqlTypesToUse.length > 1 ) { && canDoNameParameterBinding( typeToUse, procedureCall ) ) {
// // there is more than one column involved; see if the Hibernate Type can handle name = this.name;
// // multi-param extraction... }
// final boolean canHandleMultiParamExtraction = else {
// ProcedureParameterExtractionAware.class.isInstance( typeToUse ) name = null;
// && ( (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() );
// }
}
} }
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { final JdbcParameterBinder parameterBinder;
final ValueBinder<T> binder; final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final BasicType<T> basicType; final JdbcCallParameterExtractorImpl<T> parameterExtractor;
if ( typeToUse instanceof BasicType ) {
basicType = ( (BasicType<T>) typeToUse ); switch ( mode ) {
binder = basicType.getJdbcValueBinder(); 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<T> typeToUse, String name) {
if ( typeToUse instanceof BasicType<?> ) {
if ( name == null ) {
return new JdbcParameterImpl( (BasicType<T>) typeToUse );
} }
else { else {
throw new NotYetImplementedFor6Exception( getClass() ); return new JdbcParameterImpl( (BasicType<T>) typeToUse ) {
} @Override
if ( binding == null || binding.getBindValue() == null ) { protected void bindParameterValue(
// the user did not binding a value to the parameter being processed. This is the condition JdbcMapping jdbcMapping,
// defined by `passNulls` and that value controls what happens here. If `passNulls` is PreparedStatement statement,
// {@code true} we will binding the NULL value into the statement; if `passNulls` is Object bindValue,
// {@code false} we will not. int startPosition,
// ExecutionContext executionContext) throws SQLException {
// Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure jdbcMapping.getJdbcValueBinder().bind(
// parameter defines a default value. Deferring to that information would be the best option (CallableStatement) statement,
if ( ( binding != null && binding.isBound() ) || isPassNullsEnabled() ) { bindValue,
log.debugf( name,
"Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", executionContext.getSession()
procedureCall.getProcedureName(),
this
);
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
&& canDoNameParameterBinding( typeToUse, procedureCall ) ) {
//noinspection unchecked
( (ProcedureParameterNamedBinder<T>) typeToUse ).nullSafeSet(
statement,
null,
this.getName(),
procedureCall.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<T>) 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( private boolean canDoNameParameterBinding(
@ -269,4 +209,33 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
&& hibernateType instanceof ProcedureParameterNamedBinder && hibernateType instanceof ProcedureParameterNamedBinder
&& ( (ProcedureParameterNamedBinder<?>) hibernateType ).canDoSetting(); && ( (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();
}
}
} }

View File

@ -11,7 +11,9 @@ import java.util.List;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.spi.FunctionReturnImplementor;
import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor;
import org.hibernate.query.spi.ProcedureParameterMetadataImplementor; import org.hibernate.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.internal.JdbcCallImpl; import org.hibernate.sql.exec.internal.JdbcCallImpl;
@ -42,39 +44,46 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt
} }
@Override @Override
public JdbcCall interpretCall( public JdbcCall interpretCall(ProcedureCallImplementor<?> procedureCall) {
String procedureName, final String procedureName = procedureCall.getProcedureName();
FunctionReturnImpl functionReturn, final FunctionReturnImplementor functionReturn = procedureCall.getFunctionReturn();
ProcedureParameterMetadataImplementor parameterMetadata, final ProcedureParameterMetadataImplementor parameterMetadata = procedureCall.getParameterMetadata();
ProcedureParamBindings paramBindings, final SharedSessionContractImplementor session = procedureCall.getSession();
SharedSessionContractImplementor session) {
final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList(); final List<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
final StringBuilder buffer = new StringBuilder(9 + procedureName.length() + registrations.size() * 2).append( "{call " ) final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder(
.append( procedureName ) parameterMetadata.hasNamedParameters() ?
.append( "(" ); 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 = ""; String sep = "";
for ( int i = 0; i < registrations.size(); i++ ) { 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() ); 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( ")}" ); buffer.append( ")}" );
return new JdbcCallImpl.Builder( builder.setCallableName( buffer.toString() );
buffer.toString(), return builder.buildJdbcCall();
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
).buildJdbcCall();
} }
private void verifyRefCursorSupport(Dialect dialect) { private void verifyRefCursorSupport(Dialect dialect) {

View File

@ -18,15 +18,12 @@ import org.hibernate.query.results.ResultSetMapping;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.jboss.logging.Logger;
/** /**
* Utilities used to implement procedure call support. * Utilities used to implement procedure call support.
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class Util { public class Util {
private static final Logger log = Logger.getLogger( Util.class );
private Util() { private Util() {
} }
@ -85,8 +82,7 @@ public class Util {
} }
} }
else { else {
final JavaType<?> basicType = javaTypeRegistry.getDescriptor( final JavaType<?> basicType = javaTypeRegistry.getDescriptor( resultSetMappingClass );
resultSetMappingClass );
if ( basicType != null ) { if ( basicType != null ) {
resultSetMapping.addResultBuilder( new ScalarDomainResultBuilder<>( basicType ) ); resultSetMapping.addResultBuilder( new ScalarDomainResultBuilder<>( basicType ) );
} }

View File

@ -9,8 +9,6 @@ package org.hibernate.procedure.spi;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import org.hibernate.engine.spi.SharedSessionContractImplementor; 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.query.spi.ProcedureParameterMetadataImplementor;
import org.hibernate.sql.exec.spi.JdbcCall; import org.hibernate.sql.exec.spi.JdbcCall;
@ -18,16 +16,11 @@ import org.hibernate.sql.exec.spi.JdbcCall;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface CallableStatementSupport { public interface CallableStatementSupport {
JdbcCall interpretCall( JdbcCall interpretCall(ProcedureCallImplementor<?> procedureCall);
String procedureName,
FunctionReturnImpl functionReturn,
ProcedureParameterMetadataImplementor parameterMetadata,
ProcedureParamBindings paramBindings,
SharedSessionContractImplementor session);
void registerParameters( void registerParameters(
String procedureName, String procedureName,
ProcedureCallImplementor procedureCall, JdbcCall procedureCall,
CallableStatement statement, CallableStatement statement,
ParameterStrategy parameterStrategy, ParameterStrategy parameterStrategy,
ProcedureParameterMetadataImplementor parameterMetadata, ProcedureParameterMetadataImplementor parameterMetadata,

View File

@ -6,10 +6,21 @@
*/ */
package org.hibernate.procedure.spi; package org.hibernate.procedure.spi;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.procedure.FunctionReturn; import org.hibernate.procedure.FunctionReturn;
import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn;
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface FunctionReturnImplementor extends FunctionReturn, ProcedureParameterImplementor { public interface FunctionReturnImplementor<T> extends FunctionReturn<T>, ProcedureParameterImplementor<T> {
@Override
default JdbcCallParameterRegistration toJdbcParameterRegistration(
int startIndex,
ProcedureCallImplementor<?> procedureCall) {
return toJdbcFunctionReturn( procedureCall.getSession() );
}
JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor session);
} }

View File

@ -9,15 +9,17 @@ package org.hibernate.procedure.spi;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; 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.FlushModeType;
import jakarta.persistence.Parameter; import jakarta.persistence.Parameter;
import jakarta.persistence.ParameterMode; import jakarta.persistence.ParameterMode;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.type.BasicTypeReference;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -29,6 +31,11 @@ public interface ProcedureCallImplementor<R> extends ProcedureCall, QueryImpleme
ParameterStrategy getParameterStrategy(); ParameterStrategy getParameterStrategy();
FunctionReturnImplementor getFunctionReturn();
@Override
ProcedureParameterMetadataImplementor getParameterMetadata();
@Override @Override
default R getSingleResult() { default R getSingleResult() {
return uniqueResult(); return uniqueResult();

View File

@ -6,12 +6,10 @@
*/ */
package org.hibernate.procedure.spi; package org.hibernate.procedure.spi;
import java.sql.CallableStatement;
import java.sql.SQLException;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.query.procedure.ProcedureParameter;
import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
/** /**
* SPI extension for ProcedureParameter * SPI extension for ProcedureParameter
@ -20,14 +18,7 @@ import org.hibernate.query.spi.QueryParameterImplementor;
*/ */
@Incubating @Incubating
public interface ProcedureParameterImplementor<T> extends ProcedureParameter<T>, QueryParameterImplementor<T> { public interface ProcedureParameterImplementor<T> extends ProcedureParameter<T>, QueryParameterImplementor<T> {
/**
* Allow the parameter to register itself with the JDBC CallableStatement, JdbcCallParameterRegistration toJdbcParameterRegistration(int startIndex, ProcedureCallImplementor<?> procedureCall);
* 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;
} }

View File

@ -6,11 +6,11 @@
*/ */
package org.hibernate.query.procedure; package org.hibernate.query.procedure;
import jakarta.persistence.ParameterMode;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.query.QueryParameter; 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 * 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<T> extends QueryParameter<T> {
*/ */
ParameterMode getMode(); 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) {
}
} }

View File

@ -9,7 +9,6 @@ package org.hibernate.sql.exec.internal;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer; import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.JdbcMapping; 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.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
/** /**
@ -80,6 +77,7 @@ public abstract class AbstractJdbcParameter
throw new ExecutionException( "JDBC parameter value not bound - " + this ); throw new ExecutionException( "JDBC parameter value not bound - " + this );
} }
final Object bindValue = binding.getBindValue();
JdbcMapping jdbcMapping = binding.getBindType(); JdbcMapping jdbcMapping = binding.getBindType();
if ( jdbcMapping == null ) { 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 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 ) { 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 //noinspection unchecked
jdbcMapping.getJdbcValueBinder().bind( jdbcMapping.getJdbcValueBinder().bind(
statement, statement,
@ -102,13 +107,14 @@ public abstract class AbstractJdbcParameter
); );
} }
private JdbcMapping guessBindType(ExecutionContext executionContext, JdbcParameterBinding binding, JdbcMapping jdbcMapping) { private JdbcMapping guessBindType(ExecutionContext executionContext, Object bindValue, JdbcMapping jdbcMapping) {
if ( binding.getBindValue() == null && jdbcMapping != null ) { if ( bindValue == null && jdbcMapping != null ) {
return jdbcMapping; return jdbcMapping;
} }
final AllowableParameterType<?> parameterType = executionContext.getSession().getFactory() final AllowableParameterType<?> parameterType = executionContext.getSession()
.resolveParameterBindType( binding.getBindValue() ); .getFactory()
.resolveParameterBindType( bindValue );
if ( parameterType instanceof JdbcMapping ) { if ( parameterType instanceof JdbcMapping ) {
return (JdbcMapping) parameterType; return (JdbcMapping) parameterType;
} }

View File

@ -6,25 +6,23 @@
*/ */
package org.hibernate.sql.exec.internal; package org.hibernate.sql.exec.internal;
import jakarta.persistence.ParameterMode;
import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn; import org.hibernate.sql.exec.spi.JdbcCallFunctionReturn;
import jakarta.persistence.ParameterMode;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class JdbcCallFunctionReturnImpl extends JdbcCallParameterRegistrationImpl implements JdbcCallFunctionReturn { public class JdbcCallFunctionReturnImpl extends JdbcCallParameterRegistrationImpl implements JdbcCallFunctionReturn {
public JdbcCallFunctionReturnImpl( public JdbcCallFunctionReturnImpl(
int jdbcTypeCode,
AllowableParameterType ormType, AllowableParameterType ormType,
JdbcCallParameterExtractorImpl parameterExtractor, JdbcCallParameterExtractorImpl parameterExtractor,
JdbcCallRefCursorExtractorImpl refCursorExtractor) { JdbcCallRefCursorExtractorImpl refCursorExtractor) {
super( super(
null, null,
0, 1,
ParameterMode.OUT, ParameterMode.REF_CURSOR,
jdbcTypeCode,
ormType, ormType,
null, null,
parameterExtractor, parameterExtractor,

View File

@ -115,9 +115,9 @@ public class JdbcCallImpl implements JdbcCall {
} }
public static class Builder { public static class Builder {
private final String callableName;
private final ParameterStrategy parameterStrategy; private final ParameterStrategy parameterStrategy;
private String callableName;
private JdbcCallFunctionReturn functionReturn; private JdbcCallFunctionReturn functionReturn;
private List<JdbcCallParameterRegistration> parameterRegistrations; private List<JdbcCallParameterRegistration> parameterRegistrations;
@ -125,8 +125,7 @@ public class JdbcCallImpl implements JdbcCall {
private List<JdbcCallParameterExtractor> parameterExtractors; private List<JdbcCallParameterExtractor> parameterExtractors;
private List<JdbcCallRefCursorExtractor> refCursorExtractors; private List<JdbcCallRefCursorExtractor> refCursorExtractors;
public Builder(String callableName, ParameterStrategy parameterStrategy) { public Builder(ParameterStrategy parameterStrategy) {
this.callableName = callableName;
this.parameterStrategy = parameterStrategy; this.parameterStrategy = parameterStrategy;
} }
@ -134,6 +133,10 @@ public class JdbcCallImpl implements JdbcCall {
return new JdbcCallImpl( this ); return new JdbcCallImpl( this );
} }
public void setCallableName(String callableName) {
this.callableName = callableName;
}
public void setFunctionReturn(JdbcCallFunctionReturn functionReturn) { public void setFunctionReturn(JdbcCallFunctionReturn functionReturn) {
this.functionReturn = functionReturn; this.functionReturn = functionReturn;
} }
@ -149,6 +152,7 @@ public class JdbcCallImpl implements JdbcCall {
switch ( registration.getParameterMode() ) { switch ( registration.getParameterMode() ) {
case REF_CURSOR: { case REF_CURSOR: {
addParameterBinder( JdbcParameterBinder.NOOP );
addRefCursorExtractor( registration.getRefCursorExtractor() ); addRefCursorExtractor( registration.getRefCursorExtractor() );
break; break;
} }
@ -162,6 +166,7 @@ public class JdbcCallImpl implements JdbcCall {
break; break;
} }
case OUT: { case OUT: {
addParameterBinder( JdbcParameterBinder.NOOP );
addParameterExtractor( registration.getParameterExtractor() ); addParameterExtractor( registration.getParameterExtractor() );
break; break;
} }

View File

@ -10,9 +10,9 @@ import java.sql.CallableStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor; import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
/** /**
@ -20,7 +20,7 @@ import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtractor { public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtractor<T> {
private final String callableName; private final String callableName;
private final String parameterName; private final String parameterName;
private final int parameterPosition; private final int parameterPosition;
@ -58,7 +58,7 @@ public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtra
public T extractValue( public T extractValue(
CallableStatement callableStatement, CallableStatement callableStatement,
boolean shouldUseJdbcNamedParameters, boolean shouldUseJdbcNamedParameters,
ExecutionContext executionContext) { SharedSessionContractImplementor session) {
final boolean useNamed = shouldUseJdbcNamedParameters final boolean useNamed = shouldUseJdbcNamedParameters
&& parameterName != null; && parameterName != null;
@ -68,14 +68,14 @@ public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtra
try { try {
if ( useNamed ) { if ( useNamed ) {
return (T) ormType.extract( callableStatement, parameterName, executionContext.getSession() ); return (T) ormType.extract( callableStatement, parameterName, session );
} }
else { else {
return (T) ormType.extract( callableStatement, parameterPosition, executionContext.getSession() ); return (T) ormType.extract( callableStatement, parameterPosition, session );
} }
} }
catch (SQLException e) { catch (SQLException e) {
throw executionContext.getSession().getJdbcServices().getSqlExceptionHelper().convert( throw session.getJdbcServices().getSqlExceptionHelper().convert(
e, e,
"Unable to extract OUT/INOUT parameter value" "Unable to extract OUT/INOUT parameter value"
); );

View File

@ -8,7 +8,6 @@ package org.hibernate.sql.exec.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.SQLException; import java.sql.SQLException;
import jakarta.persistence.ParameterMode;
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.engine.spi.SharedSessionContractImplementor; 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.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import jakarta.persistence.ParameterMode;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -26,7 +27,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis
private final String name; private final String name;
private final int jdbcParameterPositionStart; private final int jdbcParameterPositionStart;
private final ParameterMode parameterMode; private final ParameterMode parameterMode;
private final int jdbcTypeCode;
private final AllowableParameterType ormType; private final AllowableParameterType ormType;
private final JdbcParameterBinder parameterBinder; private final JdbcParameterBinder parameterBinder;
private final JdbcCallParameterExtractorImpl parameterExtractor; private final JdbcCallParameterExtractorImpl parameterExtractor;
@ -36,7 +36,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis
String name, String name,
int jdbcParameterPositionStart, int jdbcParameterPositionStart,
ParameterMode parameterMode, ParameterMode parameterMode,
int jdbcTypeCode,
AllowableParameterType ormType, AllowableParameterType ormType,
JdbcParameterBinder parameterBinder, JdbcParameterBinder parameterBinder,
JdbcCallParameterExtractorImpl parameterExtractor, JdbcCallParameterExtractorImpl parameterExtractor,
@ -44,7 +43,6 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis
this.name = name; this.name = name;
this.jdbcParameterPositionStart = jdbcParameterPositionStart; this.jdbcParameterPositionStart = jdbcParameterPositionStart;
this.parameterMode = parameterMode; this.parameterMode = parameterMode;
this.jdbcTypeCode = jdbcTypeCode;
this.ormType = ormType; this.ormType = ormType;
this.parameterBinder = parameterBinder; this.parameterBinder = parameterBinder;
this.parameterExtractor = parameterExtractor; this.parameterExtractor = parameterExtractor;

View File

@ -31,7 +31,6 @@ public class JdbcCallRefCursorExtractorImpl implements JdbcCallRefCursorExtracto
this.jdbcParameterPosition = jdbcParameterPosition; this.jdbcParameterPosition = jdbcParameterPosition;
} }
@Override @Override
public ResultSet extractResultSet( public ResultSet extractResultSet(
CallableStatement callableStatement, CallableStatement callableStatement,

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.exec.spi;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl;
/** /**
@ -25,5 +26,5 @@ public interface JdbcCallParameterExtractor<T> {
T extractValue( T extractValue(
CallableStatement callableStatement, CallableStatement callableStatement,
boolean shouldUseJdbcNamedParameters, boolean shouldUseJdbcNamedParameters,
ExecutionContext executionContext); SharedSessionContractImplementor session);
} }

View File

@ -16,6 +16,9 @@ import java.sql.SQLException;
* @author John O'Hara * @author John O'Hara
*/ */
public interface JdbcParameterBinder { public interface JdbcParameterBinder {
JdbcParameterBinder NOOP = (statement, startPosition, jdbcParameterBindings, executionContext) -> {};
/** /**
* Bind the appropriate value in the JDBC statement * Bind the appropriate value in the JDBC statement
*/ */

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.jdbc.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;

View File

@ -357,7 +357,7 @@ public class MySQLStoredProcedureTest {
scope.inTransaction( entityManager -> { scope.inTransaction( entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ) ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" ); .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.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, null ); procedureCall.setParameter( 1, null );
@ -369,7 +369,7 @@ public class MySQLStoredProcedureTest {
scope.inTransaction( entityManager -> { scope.inTransaction( entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ) ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" ); .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.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, "test" ); procedureCall.setParameter( 1, "test" );

View File

@ -405,6 +405,23 @@ public class OracleStoredProcedureTest {
); );
} }
@Test
public void testNamedProcedureCallStoredProcedureRefCursor(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
List<Object[]> 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 @Test
public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC(EntityManagerFactoryScope scope) { public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC(EntityManagerFactoryScope scope) {
scope.inTransaction( scope.inTransaction(

View File

@ -366,7 +366,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ) ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" ); .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.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, null ); procedureCall.setParameter( 1, null );
@ -378,7 +378,7 @@ public class PostgreSQLStoredProcedureTest extends BaseEntityManagerFunctionalTe
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
ProcedureCall procedureCall = entityManager.unwrap( Session.class ) ProcedureCall procedureCall = entityManager.unwrap( Session.class )
.createStoredProcedureCall( "sp_is_null" ); .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.registerParameter( 2, Boolean.class, ParameterMode.OUT );
procedureCall.setParameter( 1, "test" ); procedureCall.setParameter( 1, "test" );

View File

@ -407,7 +407,7 @@ public class StoredProcedureParameterTypeTest {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
ProcedureCall procedureCall = session.createStoredProcedureCall( "test" ); 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 ); procedureCall.setParameter( 1, null );
} }
); );

View File

@ -14,6 +14,7 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.NamedStoredProcedureQuery; import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.ParameterMode; import jakarta.persistence.ParameterMode;
import jakarta.persistence.QueryHint;
import jakarta.persistence.StoredProcedureParameter; import jakarta.persistence.StoredProcedureParameter;
import jakarta.persistence.Table; import jakarta.persistence.Table;
@ -23,9 +24,7 @@ import jakarta.persistence.Table;
name = "NumValue.getSomeValues", name = "NumValue.getSomeValues",
procedureName = "f_test_return_cursor", procedureName = "f_test_return_cursor",
resultClasses = NumValue.class, resultClasses = NumValue.class,
parameters = { hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true")
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = ResultSet.class)
}
) )
public class NumValue implements Serializable { public class NumValue implements Serializable {
@Id @Id

View File

@ -16,15 +16,19 @@ import jakarta.persistence.FieldResult;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.NamedStoredProcedureQueries; import jakarta.persistence.NamedStoredProcedureQueries;
import jakarta.persistence.NamedStoredProcedureQuery;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderColumn; import jakarta.persistence.OrderColumn;
import jakarta.persistence.QueryHint;
import jakarta.persistence.SqlResultSetMapping; import jakarta.persistence.SqlResultSetMapping;
import jakarta.persistence.SqlResultSetMappings; import jakarta.persistence.SqlResultSetMappings;
import jakarta.persistence.StoredProcedureParameter;
import jakarta.persistence.Temporal; import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import org.hibernate.annotations.NamedNativeQuery; import org.hibernate.annotations.NamedNativeQuery;
import org.hibernate.annotations.QueryHints;
/** /**
* @author Vlad Mihalcea * @author Vlad Mihalcea
@ -41,6 +45,15 @@ import org.hibernate.annotations.NamedNativeQuery;
callable = false, callable = false,
resultSetMapping = "person_with_phones_hana" 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({ @SqlResultSetMappings({
@SqlResultSetMapping( @SqlResultSetMapping(
name = "person_with_phones", name = "person_with_phones",

View File

@ -140,7 +140,26 @@ List<Object[]> postAndComments = entityManager.createNamedQuery("fn_person_and_p
is going to throw an `IllegalArgumentException`. 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<Object[]> postAndComments = entityManager.createNamedStoredProcedureQuery( "fn_person_and_phones" ).setParameter( 1, 1L ).getResultList();
```
or not define the stored procedure and use this code
``` ```
List<Object[]> postAndComments = entityManager.createStoredProcedureQuery( "fn_person_and_phones", "person_with_phones" ).setParameter( 1, 1L ).getResultList(); List<Object[]> postAndComments = entityManager.createStoredProcedureQuery( "fn_person_and_phones", "person_with_phones" ).setParameter( 1, 1L ).getResultList();
``` ```