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";
/**
* 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.
*

View File

@ -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<? extends ProcedureParameterImplementor<?>> 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() );
if ( procedureCall.getFunctionReturn() != null ) {
procedureCall.getFunctionReturn().registerParameter( statement, session );
}
else {
refCursorSupport
.registerRefCursorParameter( statement, parameter.getPosition() );
}
}
else {
parameter.prepare( statement, i + 1, procedureCall );
}
}
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"Error registering CallableStatement parameters",
procedureName
);
for ( JdbcCallParameterRegistration parameterRegistration : procedureCall.getParameterRegistrations() ) {
parameterRegistration.registerParameter( statement, session );
}
}
}

View File

@ -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<T> implements FunctionReturnImplementor<T> {
private final ProcedureCallImplementor<T> procedureCall;
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.jdbcTypeCode = jdbcTypeCode;
}
public FunctionReturnImpl(ProcedureCallImplementor procedureCall, AllowableOutputParameterType ormType) {
public FunctionReturnImpl(ProcedureCallImplementor<T> procedureCall, AllowableOutputParameterType<T> ormType) {
this.procedureCall = procedureCall;
this.jdbcTypeCode = ormType.getJdbcTypeDescriptor().getJdbcTypeCode();
this.ormType = ormType;
}
@Override
public JdbcCallFunctionReturn toJdbcFunctionReturn(SharedSessionContractImplementor persistenceContext) {
final AllowableParameterType ormType;
final AllowableParameterType<T> ormType;
final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl parameterExtractor;
final JdbcCallParameterExtractorImpl<T> 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<T>) 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<T> 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() );
}
}

View File

@ -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<? extends ProcedureParameterImplementor<?>> registrations = parameterMetadata.getRegistrationsAsList();
final JdbcCallImpl.Builder builder = new JdbcCallImpl.Builder(
parameterMetadata.hasNamedParameters() ?
ParameterStrategy.NAMED :
ParameterStrategy.POSITIONAL
);
final StringBuilder buffer;
if ( firstParamIsRefCursor ) {
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 {
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) {

View File

@ -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<R>
private final String procedureName;
private FunctionReturnImpl functionReturn;
private FunctionReturnImpl<R> functionReturn;
private final ProcedureParameterMetadataImpl parameterMetadata;
private final ProcedureParamBindings paramBindings;
@ -93,6 +117,7 @@ public class ProcedureCallImpl<R>
private final QueryOptionsImpl queryOptions = new QueryOptionsImpl();
private JdbcCall call;
private ProcedureOutputsImpl outputs;
@ -263,6 +288,30 @@ public class ProcedureCallImpl<R>
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<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
@ -299,9 +348,14 @@ public class ProcedureCallImpl<R>
return functionReturn != null;
}
@Override
public FunctionReturnImplementor<R> 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<R>
.getJdbcEnvironment()
.getDialect()
.getCallableStatementSupport();
final ProcedureParameterMetadataImpl parameterMetadata = getParameterMetadata();
final JdbcCall call = callableStatementSupport.interpretCall(
procedureName,
functionReturn,
parameterMetadata,
paramBindings,
getSession()
);
this.call = callableStatementSupport.interpretCall( this );
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 );
final CallableStatement statement = (CallableStatement) getSession()
@ -535,35 +601,100 @@ public class ProcedureCallImpl<R>
.getStatementPreparer()
.prepareStatement( call.getSql(), true );
callableStatementSupport.registerParameters( procedureName, this,statement, parameterMetadata.getParameterStrategy(), parameterMetadata, getSession() );
// getParameterMetadata().visitRegistrations(
// new Consumer<QueryParameter<?>>() {
// 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<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
@ -618,25 +749,6 @@ public class ProcedureCallImpl<R>
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
public NamedCallableQueryMemento toMemento(String name) {
return new NamedCallableQueryMementoImpl(

View File

@ -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<ProcedureParameter<?>, JdbcCallParameterRegistration> parameterRegistrations;
private final JdbcCallRefCursorExtractor[] refCursorParameters;
private int refCursorParamIndex;
ProcedureOutputsImpl(ProcedureCallImpl procedureCall, CallableStatement callableStatement) {
ProcedureOutputsImpl(
ProcedureCallImpl procedureCall,
Map<ProcedureParameter<?>, 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> T getOutputParameterValue(ProcedureParameter<T> parameter) {
final AllowableParameterType<T> 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(
return (T) registration.getRefCursorExtractor().extractResultSet(
callableStatement,
parameter.getPosition(),
procedureCall.getSession()
);
}
else {
return (T) ( (AllowableOutputParameterType<?>) hibernateType ).extract(
//noinspection unchecked
return (T) registration.getParameterExtractor().extractValue(
callableStatement,
parameter.getName(),
parameter.getPosition() == null,
procedureCall.getSession()
);
}
}
else {
throw new ParameterMisuseException( "Parameter type cannot extract procedure output parameters" );
}
}
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 ) );
}
}

View File

@ -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<T> extends AbstractQueryParameter<T> implements ProcedureParameterImplementor<T> {
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<T> javaType;
public ProcedureParameterImpl(
String name,
ParameterMode mode,
@ -111,29 +114,9 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> 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<T> binding = procedureCall.getParameterBindings().getBinding( this );
final TypeConfiguration typeConfiguration = procedureCall.getSession().getFactory().getTypeConfiguration();
final AllowableParameterType<T> typeToUse = BindingTypeHelper.INSTANCE.resolveTemporalPrecision(
@ -144,115 +127,72 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> 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() );
final String name;
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
&& canDoNameParameterBinding( typeToUse, procedureCall ) ) {
name = this.name;
}
else {
// for ( int i = 0; i < sqlTypesToUse.length; i++ ) {
if ( position == null ) {
position = startIndex;
}
statement.registerOutParameter( startIndex, recommendedJdbcType.getJdbcTypeCode() );
// }
}
name = null;
}
if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) {
final ValueBinder<T> binder;
final BasicType<T> basicType;
if ( typeToUse instanceof BasicType ) {
basicType = ( (BasicType<T>) typeToUse );
binder = basicType.getJdbcValueBinder();
final JdbcParameterBinder parameterBinder;
final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl<T> 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<T> typeToUse, String name) {
if ( typeToUse instanceof BasicType<?> ) {
if ( name == null ) {
return new JdbcParameterImpl( (BasicType<T>) typeToUse );
}
else {
return new JdbcParameterImpl( (BasicType<T>) 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 {
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<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() );
}
}
}
}
private boolean canDoNameParameterBinding(
@ -269,4 +209,33 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> 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();
}
}
}

View File

@ -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<? extends ProcedureParameterImplementor<?>> 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 = ",";
}
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) {

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.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 ) );
}

View File

@ -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,

View File

@ -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<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.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<R> extends ProcedureCall, QueryImpleme
ParameterStrategy getParameterStrategy();
FunctionReturnImplementor getFunctionReturn();
@Override
ProcedureParameterMetadataImplementor getParameterMetadata();
@Override
default R getSingleResult() {
return uniqueResult();

View File

@ -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<T> extends ProcedureParameter<T>, QueryParameterImplementor<T> {
/**
* 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);
}

View File

@ -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<T> extends QueryParameter<T> {
*/
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.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;
}

View File

@ -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,

View File

@ -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<JdbcCallParameterRegistration> parameterRegistrations;
@ -125,8 +125,7 @@ public class JdbcCallImpl implements JdbcCall {
private List<JdbcCallParameterExtractor> parameterExtractors;
private List<JdbcCallRefCursorExtractor> 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;
}

View File

@ -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<T> implements JdbcCallParameterExtractor {
public class JdbcCallParameterExtractorImpl<T> implements JdbcCallParameterExtractor<T> {
private final String callableName;
private final String parameterName;
private final int parameterPosition;
@ -58,7 +58,7 @@ public class JdbcCallParameterExtractorImpl<T> 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<T> 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"
);

View File

@ -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;

View File

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

View File

@ -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> {
T extractValue(
CallableStatement callableStatement,
boolean shouldUseJdbcNamedParameters,
ExecutionContext executionContext);
SharedSessionContractImplementor session);
}

View File

@ -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
*/

View File

@ -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;

View File

@ -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" );

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
public void testNamedNativeQueryStoredProcedureRefCursorWithJDBC(EntityManagerFactoryScope scope) {
scope.inTransaction(

View File

@ -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" );

View File

@ -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 );
}
);

View File

@ -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

View File

@ -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",

View File

@ -140,7 +140,26 @@ List<Object[]> 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<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();
```