From 6ba328e7a03d2faaf648e85310e903f11211cbd8 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 29 Nov 2017 12:33:00 -0600 Subject: [PATCH] HHH-12136 - Various improvements for ProcedureCall/StoredProcedureQuery --- hibernate-core/src/main/antlr/hql.g | 2 +- .../hibernate/engine/spi/QueryParameters.java | 3 +- .../hibernate/procedure/ParameterBind.java | 4 +- .../procedure/ParameterRegistration.java | 12 +- .../AbstractParameterRegistrationImpl.java | 431 ------------------ .../internal/NamedParameterRegistration.java | 37 -- .../procedure/internal/ParameterBindImpl.java | 94 +++- .../PositionalParameterRegistration.java | 37 -- .../procedure/internal/ProcedureCallImpl.java | 428 +++++++---------- .../internal/ProcedureCallMementoImpl.java | 2 +- .../spi/CallableStatementSupport.java | 26 +- .../spi/ParameterRegistrationImplementor.java | 1 + .../hibernate/query/ParameterMetadata.java | 3 + .../query/internal/AbstractProducedQuery.java | 134 +++--- .../query/internal/CollectionFilterImpl.java | 12 + .../query/internal/NativeQueryImpl.java | 20 + .../query/internal/ParameterMetadataImpl.java | 23 +- .../hibernate/query/internal/QueryImpl.java | 13 + .../query/internal/QueryParameterImpl.java | 6 +- .../internal/ProcedureParamBindings.java | 135 ++++++ .../internal/ProcedureParameterImpl.java | 362 ++++++++++++++- .../internal/ProcedureParameterMetadata.java | 108 +++-- .../spi/ProcedureParameterImplementor.java | 3 +- .../query/spi/QueryParameterBindings.java | 16 + .../tck2_2/StoredProcedureApiTests.java | 140 ++++++ .../sql/storedproc/StoredProcedureTest.java | 6 +- 26 files changed, 1129 insertions(+), 929 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/StoredProcedureApiTests.java diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 08f2854b89..ac9e2a4ac3 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -813,7 +813,7 @@ identPrimaryBase ; castedIdentPrimaryBase - : i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equals("treat") }? { + : i:IDENT! OPEN! p:path AS! a:path! CLOSE! { i.getText().equalsIgnoreCase("treat") }? { registerTreat( #p, #a ); } ; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java index ea0e822d9f..36bbb2e116 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java @@ -25,6 +25,7 @@ import org.hibernate.internal.FilterImpl; import org.hibernate.internal.util.EntityPrinter; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.internal.QueryParameterBindingsImpl; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.ComponentType; import org.hibernate.type.Type; @@ -230,7 +231,7 @@ public final class QueryParameters { } public QueryParameters( - QueryParameterBindingsImpl queryParameterBindings, + QueryParameterBindings queryParameterBindings, LockOptions lockOptions, RowSelection selection, final boolean isReadOnlyInitialized, diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java index a4df1a7bd9..08eca3af77 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterBind.java @@ -8,10 +8,12 @@ package org.hibernate.procedure; import javax.persistence.TemporalType; +import org.hibernate.query.spi.QueryParameterBinding; + /** * Describes an input value binding for any IN/INOUT parameters. */ -public interface ParameterBind { +public interface ParameterBind extends QueryParameterBinding { /** * Retrieves the bound value. * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index b17fa02d5d..20fb099216 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -9,6 +9,8 @@ package org.hibernate.procedure; import javax.persistence.ParameterMode; import javax.persistence.TemporalType; +import org.hibernate.query.QueryParameter; +import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.type.Type; /** @@ -16,7 +18,7 @@ import org.hibernate.type.Type; * * @author Steve Ebersole */ -public interface ParameterRegistration { +public interface ParameterRegistration extends ProcedureParameter { /** * The name under which this parameter was registered. Can be {@code null} which should indicate that * positional registration was used (and therefore {@link #getPosition()} should return non-null. @@ -33,14 +35,6 @@ public interface ParameterRegistration { */ Integer getPosition(); - /** - * Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType} - * is called explicitly). - * - * @return The parameter Java type. - */ - Class getType(); - /** * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure * definition (is it an INPUT parameter? An OUTPUT parameter? etc). diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java deleted file mode 100644 index 31d3c5a257..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.procedure.internal; - -import java.sql.CallableStatement; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Calendar; -import java.util.Date; -import javax.persistence.ParameterMode; -import javax.persistence.TemporalType; - -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.procedure.ParameterBind; -import org.hibernate.procedure.ParameterMisuseException; -import org.hibernate.procedure.spi.ParameterRegistrationImplementor; -import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.type.CalendarDateType; -import org.hibernate.type.CalendarTimeType; -import org.hibernate.type.CalendarType; -import org.hibernate.type.ProcedureParameterExtractionAware; -import org.hibernate.type.ProcedureParameterNamedBinder; -import org.hibernate.type.Type; - -import org.jboss.logging.Logger; - -/** - * Abstract implementation of ParameterRegistration/ParameterRegistrationImplementor - * - * @author Steve Ebersole - */ -public abstract class AbstractParameterRegistrationImpl implements ParameterRegistrationImplementor { - private static final Logger log = Logger.getLogger( AbstractParameterRegistrationImpl.class ); - - private final ProcedureCallImpl procedureCall; - - private final Integer position; - private final String name; - - private final ParameterMode mode; - private final Class type; - - private ParameterBindImpl bind; - private boolean passNulls; - - private int startIndex; - private Type hibernateType; - private int[] sqlTypes; - - - // positional constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( procedureCall, position, null, mode, type, initialPassNullsSetting ); - } - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this( procedureCall, position, null, mode, type, hibernateType, initialPassNullsSetting ); - } - - - // named constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( procedureCall, null, name, mode, type, initialPassNullsSetting ); - } - - protected AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this( procedureCall, null, name, mode, type, hibernateType, initialPassNullsSetting ); - } - - - // full constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - private AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - this.procedureCall = procedureCall; - - this.position = position; - this.name = name; - - this.mode = mode; - this.type = type; - - if ( mode == ParameterMode.REF_CURSOR ) { - this.sqlTypes = new int[]{ Types.REF_CURSOR }; - } - else { - this.passNulls = initialPassNullsSetting; - setHibernateType( hibernateType ); - } - } - - private AbstractParameterRegistrationImpl( - ProcedureCallImpl procedureCall, - Integer position, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - this( - procedureCall, - position, - name, - mode, - type, - procedureCall.getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), - initialPassNullsSetting - ); - } - - protected SharedSessionContractImplementor session() { - return procedureCall.getSession(); - } - - @Override - public String getName() { - return name; - } - - @Override - public Integer getPosition() { - return position; - } - - @Override - public Class getType() { - return type; - } - - @Override - public ParameterMode getMode() { - return mode; - } - - @Override - public boolean isPassNullsEnabled() { - return passNulls; - } - - @Override - public void enablePassingNulls(boolean enabled) { - this.passNulls = enabled; - } - - @Override - public Type getHibernateType() { - return hibernateType; - } - - @Override - public void setHibernateType(Type type) { - if ( type == null ) { - throw new IllegalArgumentException( "Type cannot be null" ); - } - this.hibernateType = type; - this.sqlTypes = hibernateType.sqlTypes( session().getFactory() ); - } - - @Override - @SuppressWarnings("unchecked") - public ParameterBind getBind() { - return bind; - } - - @Override - public void bindValue(T value) { - validateBindability(); - this.bind = new ParameterBindImpl( value ); - } - - private void validateBindability() { - if ( ! canBind() ) { - throw new ParameterMisuseException( "Cannot bind value to non-input parameter : " + this ); - } - } - - private boolean canBind() { - return mode == ParameterMode.IN || mode == ParameterMode.INOUT; - } - - @Override - public void bindValue(T value, TemporalType explicitTemporalType) { - validateBindability(); - if ( explicitTemporalType != null ) { - if ( ! isDateTimeType() ) { - throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" ); - } - } - this.bind = new ParameterBindImpl( value, explicitTemporalType ); - } - - private boolean isDateTimeType() { - return Date.class.isAssignableFrom( type ) - || Calendar.class.isAssignableFrom( type ); - } - - @Override - public void prepare(CallableStatement statement, int startIndex) throws SQLException { - // initially set up the Type we will use for binding as the explicit type. - Type typeToUse = hibernateType; - int[] sqlTypesToUse = sqlTypes; - - // however, for Calendar binding with an explicit TemporalType we may need to adjust this... - if ( bind != null && bind.getExplicitTemporalType() != null ) { - if ( Calendar.class.isInstance( bind.getValue() ) ) { - switch ( bind.getExplicitTemporalType() ) { - case TIMESTAMP: { - typeToUse = CalendarType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - case DATE: { - typeToUse = CalendarDateType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - case TIME: { - typeToUse = CalendarTimeType.INSTANCE; - sqlTypesToUse = typeToUse.sqlTypes( session().getFactory() ); - break; - } - } - } - } - - this.startIndex = startIndex; - if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - 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( hibernateType ) - && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction(); - if ( ! canHandleMultiParamExtraction ) { - // it cannot... - throw new UnsupportedOperationException( - "Type [" + hibernateType + "] 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. - if ( sqlTypesToUse.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding() ) { - statement.registerOutParameter( getName(), sqlTypesToUse[0] ); - } - else { - for ( int i = 0; i < sqlTypesToUse.length; i++ ) { - statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); - } - } - } - - if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { - if ( bind == null || bind.getValue() == null ) { - // the user did not bind 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 bind 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 ( passNulls ) { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", - procedureCall.getProcedureName(), - this - ); - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding() ) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - null, - this.getName(), - session() - ); - } - else { - typeToUse.nullSafeSet( statement, null, startIndex, session() ); - } - } - else { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", - procedureCall.getProcedureName(), - this - ); - } - } - else { - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding()) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - bind.getValue(), - this.getName(), - session() - ); - } - else { - typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); - } - } - } - } - else { - // we have a REF_CURSOR type param - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, getName() ); - } - else { - session().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, startIndex ); - } - } - } - - private boolean canDoNameParameterBinding() { - final ExtractedDatabaseMetaData databaseMetaData = session() - .getJdbcCoordinator() - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry().getService( JdbcEnvironment.class ) - .getExtractedDatabaseMetaData(); - return - databaseMetaData.supportsNamedParameters() && - ProcedureParameterNamedBinder.class.isInstance( hibernateType ) - && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); - } - - public int[] getSqlTypes() { - if ( mode == ParameterMode.REF_CURSOR ) { - // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... - throw new IllegalStateException( "REF_CURSOR parameters do not have a SQL/JDBC type" ); - } - return sqlTypes; - } - - @Override - @SuppressWarnings("unchecked") - public T extract(CallableStatement statement) { - if ( mode == ParameterMode.IN ) { - throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); - } - - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // For now, if sqlTypes.length > 1 with a named parameter, then extract - // parameter values by position (since we only have one name). - final boolean useNamed = sqlTypes.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding(); - - try { - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { - if ( useNamed ) { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - new String[] { getName() }, - session() - ); - } - else { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - startIndex, - session() - ); - } - } - else { - if ( useNamed ) { - return (T) statement.getObject( name ); - } - else { - return (T) statement.getObject( startIndex ); - } - } - } - catch (SQLException e) { - throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( - e, - "Unable to extract OUT/INOUT parameter value" - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java deleted file mode 100644 index a12b6ba38a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.procedure.internal; - -import javax.persistence.ParameterMode; - -import org.hibernate.type.Type; - -/** - * Represents a registered named parameter - * - * @author Steve Ebersole - */ -public class NamedParameterRegistration extends AbstractParameterRegistrationImpl { - NamedParameterRegistration( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - super( procedureCall, name, mode, type, initialPassNullsSetting ); - } - - NamedParameterRegistration( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - super( procedureCall, name, mode, type, hibernateType, initialPassNullsSetting ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java index 6ffda13a76..aa464ac77c 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java @@ -6,9 +6,16 @@ */ package org.hibernate.procedure.internal; +import javax.persistence.ParameterMode; import javax.persistence.TemporalType; import org.hibernate.procedure.ParameterBind; +import org.hibernate.query.internal.BindingTypeHelper; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.type.Type; + +import org.jboss.logging.Logger; /** * Implementation of the {@link ParameterBind} contract. @@ -16,16 +23,25 @@ import org.hibernate.procedure.ParameterBind; * @author Steve Ebersole */ public class ParameterBindImpl implements ParameterBind { - private final T value; - private final TemporalType explicitTemporalType; + private static final Logger log = Logger.getLogger( ParameterBindImpl.class ); - ParameterBindImpl(T value) { - this( value, null ); - } + private final ProcedureParameterImplementor procedureParameter; + private final ProcedureParamBindings procedureParamBindings; - ParameterBindImpl(T value, TemporalType explicitTemporalType) { - this.value = value; - this.explicitTemporalType = explicitTemporalType; + private boolean isBound; + + private T value; + private Type hibernateType; + + private TemporalType explicitTemporalType; + + public ParameterBindImpl( + ProcedureParameterImplementor procedureParameter, + ProcedureParamBindings procedureParamBindings) { + this.procedureParameter = procedureParameter; + this.procedureParamBindings = procedureParamBindings; + + this.hibernateType = procedureParameter.getHibernateType(); } @Override @@ -37,4 +53,66 @@ public class ParameterBindImpl implements ParameterBind { public TemporalType getExplicitTemporalType() { return explicitTemporalType; } + + @Override + public boolean isBound() { + return isBound; + } + + @Override + public void setBindValue(T value) { + internalSetValue( value ); + + if ( value != null && hibernateType == null ) { + hibernateType = procedureParamBindings.getProcedureCall() + .getSession() + .getFactory() + .getTypeResolver() + .heuristicType( value.getClass().getName() ); + log.debugf( "Using heuristic type [%s] based on bind value [%s] as `bindType`", hibernateType, value ); + } + } + + private void internalSetValue(T value) { + if ( procedureParameter.getMode() != ParameterMode.IN && procedureParameter.getMode() != ParameterMode.INOUT ) { + throw new IllegalStateException( "Can only bind values for IN/INOUT parameters : " + procedureParameter ); + } + + if ( procedureParameter.getParameterType() != null ) { + if ( !procedureParameter.getParameterType().isInstance( value ) ) { + throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter.getParameterType() ); + } + } + + this.value = value; + this.isBound = true; + } + + @Override + public void setBindValue(T value, Type clarifiedType) { + internalSetValue( value ); + this.hibernateType = clarifiedType; + log.debugf( "Using explicit type [%s] as `bindType`", hibernateType, value ); + } + + @Override + public void setBindValue(T value, TemporalType clarifiedTemporalType) { + internalSetValue( value ); + this.hibernateType = BindingTypeHelper.INSTANCE.determineTypeForTemporalType( clarifiedTemporalType, hibernateType, value ); + this.explicitTemporalType = clarifiedTemporalType; + log.debugf( "Using type [%s] (based on TemporalType [%s] as `bindType`", hibernateType, clarifiedTemporalType ); + } + + @Override + public T getBindValue() { + if ( !isBound ) { + throw new IllegalStateException( "Value not yet bound" ); + } + return value; + } + + @Override + public Type getBindType() { + return hibernateType; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java deleted file mode 100644 index 515c3f169b..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.procedure.internal; - -import javax.persistence.ParameterMode; - -import org.hibernate.type.Type; - -/** - * Represents a registered positional parameter - * - * @author Steve Ebersole - */ -public class PositionalParameterRegistration extends AbstractParameterRegistrationImpl { - PositionalParameterRegistration( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - boolean initialPassNullsSetting) { - super( procedureCall, position, mode, type, initialPassNullsSetting ); - } - - PositionalParameterRegistration( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class type, - Type hibernateType, - boolean initialPassNullsSetting) { - super( procedureCall, position, mode, type, hibernateType, initialPassNullsSetting ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 56b8bb7754..3de67ddbce 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -11,13 +11,13 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; import javax.persistence.NoResultException; @@ -28,17 +28,13 @@ import javax.persistence.TemporalType; import javax.persistence.TransactionRequiredException; import org.hibernate.HibernateException; -import org.hibernate.QueryException; import org.hibernate.engine.ResultSetMappingDefinition; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; -import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.procedure.NoSuchParameterException; import org.hibernate.procedure.ParameterRegistration; @@ -51,8 +47,12 @@ import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.AbstractProducedQuery; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; import org.hibernate.query.procedure.internal.ProcedureParameterImpl; import org.hibernate.query.procedure.internal.ProcedureParameterMetadata; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.result.NoMoreReturnsException; import org.hibernate.result.Output; import org.hibernate.result.ResultSetOutput; @@ -82,8 +82,8 @@ public class ProcedureCallImpl private final boolean globalParameterPassNullsSetting; - private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; - private List> registeredParameters = new ArrayList<>(); + private final ProcedureParameterMetadata parameterMetadata; + private final ProcedureParamBindings paramBindings; private Set synchronizedQuerySpaces; @@ -96,11 +96,14 @@ public class ProcedureCallImpl * @param procedureName The name of the procedure to call */ public ProcedureCallImpl(SharedSessionContractImplementor session, String procedureName) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); this.queryReturns = NO_RETURNS; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -111,7 +114,7 @@ public class ProcedureCallImpl * @param resultClasses The classes making up the result */ public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, Class... resultClasses) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); @@ -140,6 +143,9 @@ public class ProcedureCallImpl this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] ); this.synchronizedQuerySpaces = collectedQuerySpaces; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -150,7 +156,7 @@ public class ProcedureCallImpl * @param resultSetMappings The names of the result set mappings making up the result */ public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, String... resultSetMappings) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = procedureName; this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); @@ -184,6 +190,9 @@ public class ProcedureCallImpl this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] ); this.synchronizedQuerySpaces = collectedQuerySpaces; + + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); } /** @@ -194,41 +203,21 @@ public class ProcedureCallImpl */ @SuppressWarnings("unchecked") ProcedureCallImpl(SharedSessionContractImplementor session, ProcedureCallMementoImpl memento) { - super( session, new ProcedureParameterMetadata() ); + super( session, null ); this.procedureName = memento.getProcedureName(); this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled(); this.queryReturns = memento.getQueryReturns(); this.synchronizedQuerySpaces = Util.copy( memento.getSynchronizedQuerySpaces() ); - this.parameterStrategy = memento.getParameterStrategy(); - if ( parameterStrategy == ParameterStrategy.UNKNOWN ) { - // nothing else to do in this case - return; - } - final List storedRegistrations = memento.getParameterDeclarations(); - if ( storedRegistrations == null ) { - // most likely a problem if ParameterStrategy is not UNKNOWN... - LOG.debugf( - "ParameterStrategy was [%s] on named copy [%s], but no parameters stored", - parameterStrategy, - procedureName - ); - return; - } + this.parameterMetadata = new ProcedureParameterMetadata( this ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); - final List> parameterRegistrations = - CollectionHelper.arrayList( storedRegistrations.size() ); + for ( ProcedureCallMementoImpl.ParameterMemento storedRegistration : memento.getParameterDeclarations() ) { + final ProcedureParameterImplementor registration; - for ( ProcedureCallMementoImpl.ParameterMemento storedRegistration : storedRegistrations ) { - final ParameterRegistrationImplementor registration; if ( StringHelper.isNotEmpty( storedRegistration.getName() ) ) { - if ( parameterStrategy != ParameterStrategy.NAMED ) { - throw new IllegalStateException( - "Found named stored procedure parameter associated with positional parameters" - ); - } - registration = new NamedParameterRegistration( + registration = new ProcedureParameterImpl( this, storedRegistration.getName(), storedRegistration.getMode(), @@ -238,12 +227,7 @@ public class ProcedureCallImpl ); } else { - if ( parameterStrategy != ParameterStrategy.POSITIONAL ) { - throw new IllegalStateException( - "Found named stored procedure parameter associated with positional parameters" - ); - } - registration = new PositionalParameterRegistration( + registration = new ProcedureParameterImpl( this, storedRegistration.getPosition(), storedRegistration.getMode(), @@ -252,10 +236,9 @@ public class ProcedureCallImpl storedRegistration.isPassNullsEnabled() ); } - getParameterMetadata().registerParameter( new ProcedureParameterImpl( registration ) ); - parameterRegistrations.add( registration ); + + getParameterMetadata().registerParameter( registration ); } - this.registeredParameters = parameterRegistrations; for ( Map.Entry entry : memento.getHintsMap().entrySet() ) { setHint( entry.getKey(), entry.getValue() ); @@ -264,7 +247,12 @@ public class ProcedureCallImpl @Override public ProcedureParameterMetadata getParameterMetadata() { - return (ProcedureParameterMetadata) super.getParameterMetadata(); + return parameterMetadata; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return paramBindings; } @Override @@ -273,7 +261,7 @@ public class ProcedureCallImpl } public ParameterStrategy getParameterStrategy() { - return parameterStrategy; + return getParameterMetadata().getParameterStrategy(); } @Override @@ -294,11 +282,17 @@ public class ProcedureCallImpl @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(int position, Class type, ParameterMode mode) { + final ProcedureParameterImpl procedureParameter = new ProcedureParameterImpl( + this, + position, + mode, + type, + getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), + globalParameterPassNullsSetting + ); - final PositionalParameterRegistration parameterRegistration = - new PositionalParameterRegistration( this, position, mode, type, globalParameterPassNullsSetting ); - registerParameter( parameterRegistration ); - return parameterRegistration; + registerParameter( procedureParameter ); + return procedureParameter; } @Override @@ -308,68 +302,30 @@ public class ProcedureCallImpl return this; } - private void registerParameter(ParameterRegistrationImplementor parameter) { - if ( StringHelper.isNotEmpty( parameter.getName() ) ) { - prepareForNamedParameters(); - } - else if ( parameter.getPosition() != null ) { - prepareForPositionalParameters(); - } - else { - throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" ); - } - ((ProcedureParameterMetadata)getParameterMetadata()).registerParameter( new ProcedureParameterImpl( parameter ) ); - - registeredParameters.add( parameter ); - } - - private void prepareForPositionalParameters() { - if ( parameterStrategy == ParameterStrategy.NAMED ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - parameterStrategy = ParameterStrategy.POSITIONAL; - } - - private void prepareForNamedParameters() { - if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { - throw new QueryException( "Cannot mix named and positional parameters" ); - } - if ( parameterStrategy == ParameterStrategy.UNKNOWN ) { - // protect to only do this check once - final ExtractedDatabaseMetaData databaseMetaData = getSession() - .getJdbcCoordinator() - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry().getService( JdbcEnvironment.class ) - .getExtractedDatabaseMetaData(); - if ( ! databaseMetaData.supportsNamedParameters() ) { - LOG.unsupportedNamedParameters(); - } - parameterStrategy = ParameterStrategy.NAMED; - } + private void registerParameter(ProcedureParameterImplementor parameter) { + getParameterMetadata().registerParameter( parameter ); } @Override public ParameterRegistrationImplementor getParameterRegistration(int position) { - if ( parameterStrategy != ParameterStrategy.POSITIONAL ) { - throw new ParameterStrategyException( - "Attempt to access positional parameter [" + position + "] but ProcedureCall using named parameters" - ); - } - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - if ( position == parameter.getPosition() ) { - return parameter; - } - } - throw new NoSuchParameterException( "Could not locate parameter registered using that position [" + position + "]" ); + return getParameterMetadata().getQueryParameter( position ); } @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(String name, Class type, ParameterMode mode) { - final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, mode, type, globalParameterPassNullsSetting ); - registerParameter( parameterRegistration ); - return parameterRegistration; + final ProcedureParameterImpl parameter = new ProcedureParameterImpl( + this, + name, + mode, + type, + getSession().getFactory().getTypeResolver().heuristicType( type.getName() ), + globalParameterPassNullsSetting + ); + + registerParameter( parameter ); + + return parameter; } @Override @@ -381,21 +337,13 @@ public class ProcedureCallImpl @Override public ParameterRegistrationImplementor getParameterRegistration(String name) { - if ( parameterStrategy != ParameterStrategy.NAMED ) { - throw new ParameterStrategyException( "Names were not used to register parameters with this stored procedure call" ); - } - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - if ( name.equals( parameter.getName() ) ) { - return parameter; - } - } - throw new NoSuchParameterException( "Could not locate parameter registered under that name [" + name + "]" ); + return getParameterMetadata().getQueryParameter( name ); } @Override @SuppressWarnings("unchecked") - public List getRegisteredParameters() { - return new ArrayList<>( registeredParameters ); + public List getRegisteredParameters() { + return new ArrayList( getParameterMetadata().collectAllParameters() ); } @Override @@ -424,41 +372,48 @@ public class ProcedureCallImpl final String call = getProducer().getJdbcServices().getJdbcEnvironment().getDialect().getCallableStatementSupport().renderCallableStatement( procedureName, - parameterStrategy, - registeredParameters, + getParameterMetadata(), + paramBindings, getProducer() ); - try { - LOG.debugf( "Preparing procedure call : %s", call ); - final CallableStatement statement = (CallableStatement) getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( call, true ); + LOG.debugf( "Preparing procedure call : %s", call ); + final CallableStatement statement = (CallableStatement) getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( call, true ); - // prepare parameters - int i = 1; + // prepare parameters - for ( ParameterRegistrationImplementor parameter : registeredParameters ) { - parameter.prepare( statement, i ); - if ( parameter.getMode() == ParameterMode.REF_CURSOR ) { - i++; + getParameterMetadata().visitRegistrations( + new Consumer() { + int i = 1; + + @Override + public void accept(QueryParameter queryParameter) { + try { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + registration.prepare( statement, i ); + if ( registration.getMode() == ParameterMode.REF_CURSOR ) { + i++; + } + else { + i += registration.getSqlTypes().length; + } + } + catch (SQLException e) { + throw getSession().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Error preparing registered callable parameter", + getProcedureName() + ); + } + } } - else { - i += parameter.getSqlTypes().length; - } - } + ); - return new ProcedureOutputsImpl( this, statement ); - } - catch (SQLException e) { - throw getSession().getJdbcServices().getSqlExceptionHelper().convert( - e, - "Error preparing CallableStatement", - getProcedureName() - ); - } + return new ProcedureOutputsImpl( this, statement ); } @Override @@ -492,6 +447,7 @@ public class ProcedureCallImpl * * @return The spaces */ + @SuppressWarnings("WeakerAccess") protected Set synchronizedQuerySpaces() { if ( synchronizedQuerySpaces == null ) { synchronizedQuerySpaces = new HashSet<>(); @@ -522,6 +478,7 @@ public class ProcedureCallImpl return this; } + @SuppressWarnings("WeakerAccess") protected void addSynchronizedQuerySpaces(EntityPersister persister) { synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); } @@ -553,11 +510,15 @@ public class ProcedureCallImpl */ public ParameterRegistrationImplementor[] collectRefCursorParameters() { final List refCursorParams = new ArrayList<>(); - for ( ParameterRegistrationImplementor param : registeredParameters ) { - if ( param.getMode() == ParameterMode.REF_CURSOR ) { - refCursorParams.add( param ); - } - } + + getParameterMetadata().visitRegistrations( + queryParameter -> { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + if ( registration.getMode() == ParameterMode.REF_CURSOR ) { + refCursorParams.add( registration ); + } + } + ); return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] ); } @@ -566,8 +527,8 @@ public class ProcedureCallImpl return new ProcedureCallMementoImpl( procedureName, Util.copy( queryReturns ), - parameterStrategy, - toParameterMementos( registeredParameters ), + getParameterMetadata().getParameterStrategy(), + toParameterMementos( getParameterMetadata() ), Util.copy( synchronizedQuerySpaces ), Util.copy( hints ) ); @@ -578,22 +539,28 @@ public class ProcedureCallImpl return new ProcedureCallMementoImpl( procedureName, Util.copy( queryReturns ), - parameterStrategy, - toParameterMementos( registeredParameters ), + getParameterMetadata().getParameterStrategy(), + toParameterMementos( getParameterMetadata() ), Util.copy( synchronizedQuerySpaces ), Util.copy( getHints() ) ); } - private static List toParameterMementos(List> registeredParameters) { - if ( registeredParameters == null ) { - return null; + private static List toParameterMementos(ProcedureParameterMetadata parameterMetadata) { + if ( parameterMetadata.getParameterStrategy() == ParameterStrategy.UNKNOWN ) { + // none... + return Collections.emptyList(); } - final List copy = CollectionHelper.arrayList( registeredParameters.size() ); - for ( ParameterRegistrationImplementor registration : registeredParameters ) { - copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) ); - } + final List copy = new ArrayList<>(); + + parameterMetadata.visitRegistrations( + queryParameter -> { + final ParameterRegistrationImplementor registration = (ParameterRegistrationImplementor) queryParameter; + copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) ); + } + ); + return copy; } @@ -604,6 +571,7 @@ public class ProcedureCallImpl private ProcedureOutputs procedureResult; @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { getProducer().checkOpen( true ); @@ -622,6 +590,7 @@ public class ProcedureCallImpl } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { getProducer().checkOpen( true ); try { @@ -828,182 +797,127 @@ public class ProcedureCallImpl return this; } + // todo (5.3) : all of the parameter stuff here can be done in AbstractProducedQuery + // using #getParameterMetadata and #getQueryParameterBindings for abstraction. + // this "win" is to define these in one place + @Override public

ProcedureCallImplementor setParameter(QueryParameter

parameter, P value) { - locateParameterRegistration( parameter ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ).setBindValue( value ); return this; } - @SuppressWarnings("unchecked") - private

ParameterRegistrationImplementor

locateParameterRegistration(Parameter

parameter) { - if ( parameter.getName() != null ) { - return locateParameterRegistration( parameter.getName() ); - } - - if ( parameter.getPosition() != null ) { - return locateParameterRegistration( parameter.getPosition() ); - } - - throw getExceptionConverter().convert( - new IllegalArgumentException( "Could not resolve registration for given parameter reference [" + parameter + "]" ) - ); - } - - @SuppressWarnings("unchecked") - private

ParameterRegistrationImplementor

locateParameterRegistration(String name) { - assert name != null; - - if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { - throw new IllegalArgumentException( "Expecting positional parameter" ); - } - - for ( ParameterRegistrationImplementor registeredParameter : registeredParameters ) { - if ( name.equals( registeredParameter.getName() ) ) { - return (ParameterRegistrationImplementor

) registeredParameter; - } - } - - throw new IllegalArgumentException( "Unknown parameter registration name [" + name + "]" ); - } - - @SuppressWarnings("unchecked") - private

ParameterRegistrationImplementor

locateParameterRegistration(int position) { - if ( parameterStrategy == ParameterStrategy.NAMED ) { - throw new IllegalArgumentException( "Expecting named parameter" ); - } - - for ( ParameterRegistrationImplementor registeredParameter : registeredParameters ) { - if ( registeredParameter.getPosition() != null && registeredParameter.getPosition() == position ) { - return (ParameterRegistrationImplementor

) registeredParameter; - } - } - - throw new IllegalArgumentException( "Unknown parameter registration position [" + position + "]" ); - } - @Override public

ProcedureCallImplementor setParameter(Parameter

parameter, P value) { - locateParameterRegistration( parameter ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ).setBindValue( value ); return this; } @Override public ProcedureCallImplementor setParameter(String name, Object value) { - locateParameterRegistration( name ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ).setBindValue( value ); return this; } @Override public ProcedureCallImplementor setParameter(int position, Object value) { - locateParameterRegistration( position ).bindValue( value ); + paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ).setBindValue( value ); return this; } @Override public

ProcedureCallImplementor setParameter(QueryParameter

parameter, P value, Type type) { - final ParameterRegistrationImplementor

reg = locateParameterRegistration( parameter ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding

binding = paramBindings.getBinding( parameter ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Object value, Type type) { - final ParameterRegistrationImplementor reg = locateParameterRegistration( name ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Object value, Type type) { - final ParameterRegistrationImplementor reg = locateParameterRegistration( position ); - reg.bindValue( value ); - reg.setHibernateType( type ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ); + binding.setBindValue( value, type ); return this; } @Override + @SuppressWarnings("unchecked") public

ProcedureCallImplementor setParameter(QueryParameter

parameter, P value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( parameter ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Object value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( name ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Object value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(QueryParameter parameter, Collection values) { - super.setParameterList( parameter, values ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Collection values) { - super.setParameterList( name, values ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Collection values, Type type) { - super.setParameterList( name, values, type ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Object[] values, Type type) { - super.setParameterList( name, values, type ); - return this; - } - - @Override - public ProcedureCallImplementor setParameterList(String name, Object[] values) { - super.setParameterList( name, values ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().getQueryParameter( position ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(Parameter parameter, Calendar value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(Parameter parameter, Date value, TemporalType temporalType) { - locateParameterRegistration( parameter ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( getParameterMetadata().resolve( parameter ) ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Calendar value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( name ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(String name, Date value, TemporalType temporalType) { - locateParameterRegistration( name ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( name ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Calendar value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( position ); + binding.setBindValue( value, temporalType ); return this; } @Override + @SuppressWarnings("unchecked") public ProcedureCallImplementor setParameter(int position, Date value, TemporalType temporalType) { - locateParameterRegistration( position ).bindValue( value, temporalType ); + final QueryParameterBinding binding = paramBindings.getBinding( position ); + binding.setBindValue( value, temporalType ); return this; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java index 3ebab8379a..7817a432b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java @@ -162,7 +162,7 @@ public class ProcedureCallMementoImpl implements ProcedureCallMemento { registration.getPosition(), registration.getName(), registration.getMode(), - registration.getType(), + registration.getParameterType(), registration.getHibernateType(), registration.isPassNullsEnabled() ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java index e0d626bbd4..940162d229 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java @@ -7,24 +7,44 @@ package org.hibernate.procedure.spi; import java.sql.CallableStatement; +import java.util.ArrayList; import java.util.List; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.procedure.internal.ProcedureParamBindings; +import org.hibernate.query.procedure.internal.ProcedureParameterMetadata; /** * @author Steve Ebersole */ public interface CallableStatementSupport { - String renderCallableStatement( + default String renderCallableStatement( String name, ParameterStrategy parameterStrategy, List> parameterRegistrations, - SharedSessionContractImplementor session); + SharedSessionContractImplementor session) { + throw new UnsupportedOperationException( + "Legacy #renderCallableStatement called but implementation does not support that call." + ); + } + + default String renderCallableStatement( + String procedureName, + ProcedureParameterMetadata parameterMetadata, + ProcedureParamBindings paramBindings, + SharedSessionContractImplementor session) { + return renderCallableStatement( + procedureName, + parameterMetadata.getParameterStrategy(), + new ArrayList( parameterMetadata.collectAllParameters() ), + session + ); + } void registerParameters( String procedureName, CallableStatement statement, ParameterStrategy parameterStrategy, List> parameterRegistrations, - SharedSessionContractImplementor session); + SharedSessionContractImplementor session);; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java index 67096b284e..805662fe1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ParameterRegistrationImplementor.java @@ -10,6 +10,7 @@ import java.sql.CallableStatement; import java.sql.SQLException; import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.query.QueryParameter; import org.hibernate.type.Type; /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java b/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java index 3bc3364044..c56f4c7221 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/query/ParameterMetadata.java @@ -8,6 +8,7 @@ package org.hibernate.query; import java.util.Collection; import java.util.Set; +import java.util.function.Consumer; import javax.persistence.Parameter; /** @@ -62,4 +63,6 @@ public interface ParameterMetadata { int getParameterCount(); boolean containsReference(QueryParameter parameter); + + void visitRegistrations(Consumer action); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 405b4eb1bf..2a4c75c17e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -44,7 +44,6 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.NonUniqueResultException; import org.hibernate.PropertyNotFoundException; -import org.hibernate.QueryException; import org.hibernate.QueryParameterException; import org.hibernate.ScrollMode; import org.hibernate.TypeMismatchException; @@ -75,6 +74,7 @@ import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterListBinding; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.transform.ResultTransformer; @@ -110,7 +110,6 @@ public abstract class AbstractProducedQuery implements QueryImplementor { private final SharedSessionContractImplementor producer; private final ParameterMetadata parameterMetadata; - private final QueryParameterBindingsImpl queryParameterBindings; private FlushMode flushMode; private CacheStoreMode cacheStoreMode; @@ -140,11 +139,6 @@ public abstract class AbstractProducedQuery implements QueryImplementor { ParameterMetadata parameterMetadata) { this.producer = producer; this.parameterMetadata = parameterMetadata; - this.queryParameterBindings = QueryParameterBindingsImpl.from( - parameterMetadata, - producer.getFactory(), - producer.isQueryParametersValidationEnabled() - ); } @Override @@ -394,28 +388,32 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @Override public QueryImplementor setParameter(int position, OffsetDateTime value, TemporalType temporalType) { - locateBinding( position ).setBindValue( value, temporalType ); + final QueryParameterBinding binding = getQueryParameterBindings().getBinding( + getParameterMetadata().getQueryParameter( position ) + ); + + binding.setBindValue( value, temporalType ); + return this; } @Override @SuppressWarnings("unchecked") public

QueryImplementor setParameter(QueryParameter

parameter, P value) { - queryParameterBindings.getBinding( (QueryParameter) parameter ); - locateBinding( parameter ).setBindValue( value ); + getQueryParameterBindings().getBinding( (QueryParameter) parameter ).setBindValue( value ); return this; } @SuppressWarnings("unchecked") private

QueryParameterBinding

locateBinding(Parameter

parameter) { if ( parameter instanceof QueryParameter ) { - return queryParameterBindings.getBinding( (QueryParameter) parameter ); + return getQueryParameterBindings().getBinding( (QueryParameter) parameter ); } else if ( parameter.getName() != null ) { - return queryParameterBindings.getBinding( parameter.getName() ); + return getQueryParameterBindings().getBinding( parameter.getName() ); } else if ( parameter.getPosition() != null ) { - return queryParameterBindings.getBinding( parameter.getPosition() ); + return getQueryParameterBindings().getBinding( parameter.getPosition() ); } throw getExceptionConverter().convert( @@ -424,15 +422,15 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } private

QueryParameterBinding

locateBinding(QueryParameter

parameter) { - return queryParameterBindings.getBinding( parameter ); + return getQueryParameterBindings().getBinding( parameter ); } private

QueryParameterBinding

locateBinding(String name) { - return queryParameterBindings.getBinding( name ); + return getQueryParameterBindings().getBinding( name ); } private

QueryParameterBinding

locateBinding(int position) { - return queryParameterBindings.getBinding( position ); + return getQueryParameterBindings().getBinding( position ); } @Override @@ -470,15 +468,15 @@ public abstract class AbstractProducedQuery implements QueryImplementor { private QueryParameterListBinding locateListBinding(Parameter parameter) { if ( parameter instanceof QueryParameter ) { - return queryParameterBindings.getQueryParameterListBinding( (QueryParameter) parameter ); + return getQueryParameterBindings().getQueryParameterListBinding( (QueryParameter) parameter ); } else { - return queryParameterBindings.getQueryParameterListBinding( parameter.getName() ); + return getQueryParameterBindings().getQueryParameterListBinding( parameter.getName() ); } } private QueryParameterListBinding locateListBinding(String name) { - return queryParameterBindings.getQueryParameterListBinding( name ); + return getQueryParameterBindings().getQueryParameterListBinding( name ); } @Override @@ -493,7 +491,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { setParameterList( name, (Collection) value ); } else { - queryParameterBindings.getBinding( name ).setBindValue( value ); + getQueryParameterBindings().getBinding( name ).setBindValue( value ); } return this; @@ -508,10 +506,10 @@ public abstract class AbstractProducedQuery implements QueryImplementor { setParameter( position, typedParameterValue.getValue(), typedParameterValue.getType() ); } else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) { - setParameterList( parameterMetadata.getQueryParameter( position ), (Collection) value ); + setParameterList( getParameterMetadata().getQueryParameter( position ), (Collection) value ); } else { - queryParameterBindings.getBinding( position ).setBindValue( value ); + getQueryParameterBindings().getBinding( position ).setBindValue( value ); } return this; } @@ -519,105 +517,105 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @Override @SuppressWarnings("unchecked") public

QueryImplementor setParameter(QueryParameter

parameter, P value, Type type) { - queryParameterBindings.getBinding( parameter ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( parameter ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Object value, Type type) { - queryParameterBindings.getBinding( name ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Object value, Type type) { - queryParameterBindings.getBinding( position ).setBindValue( value, type ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, type ); return this; } @Override @SuppressWarnings("unchecked") public

QueryImplementor setParameter(QueryParameter

parameter, P value, TemporalType temporalType) { - queryParameterBindings.getBinding( parameter ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( parameter ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Object value, TemporalType temporalType) { - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Object value, TemporalType temporalType) { - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @Override @SuppressWarnings("unchecked") public

QueryImplementor setParameterList(QueryParameter

parameter, Collection

values) { - queryParameterBindings.getQueryParameterListBinding( parameter ).setBindValues( values ); + getQueryParameterBindings().getQueryParameterListBinding( parameter ).setBindValues( values ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Collection values) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( values ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( values ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(int position, Collection values) { - queryParameterBindings.getQueryParameterListBinding( position ).setBindValues( values ); + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( values ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Collection values, Type type) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( values, type ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( values, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(int position, Collection values, Type type) { - queryParameterBindings.getQueryParameterListBinding( position ).setBindValues( values, type ); + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( values, type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Object[] values, Type type) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ), type ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ), type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(int position, Object[] values, Type type) { - queryParameterBindings.getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ), type ); + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ), type ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(String name, Object[] values) { - queryParameterBindings.getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ) ); + getQueryParameterBindings().getQueryParameterListBinding( name ).setBindValues( Arrays.asList( values ) ); return this; } @Override @SuppressWarnings("unchecked") public QueryImplementor setParameterList(int position, Object[] values) { - queryParameterBindings.getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ) ); + getQueryParameterBindings().getQueryParameterListBinding( position ).setBindValues( Arrays.asList( values ) ); return this; } @@ -625,7 +623,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(Parameter param, Calendar value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); return this; } @@ -633,7 +631,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(Parameter param, Date value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( (QueryParameter) param ).setBindValue( value, temporalType ); return this; } @@ -641,7 +639,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Calendar value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @@ -649,7 +647,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(String name, Date value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( name ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( name ).setBindValue( value, temporalType ); return this; } @@ -657,7 +655,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Calendar value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @@ -665,7 +663,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @SuppressWarnings("unchecked") public QueryImplementor setParameter(int position, Date value, TemporalType temporalType) { getProducer().checkOpen(); - queryParameterBindings.getBinding( position ).setBindValue( value, temporalType ); + getQueryParameterBindings().getBinding( position ).setBindValue( value, temporalType ); return this; } @@ -676,7 +674,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } @Override - public Parameter getParameter(String name) { + public QueryParameter getParameter(String name) { getProducer().checkOpen( false ); try { return getParameterMetadata().getQueryParameter( name ); @@ -688,7 +686,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @Override @SuppressWarnings("unchecked") - public Parameter getParameter(String name, Class type) { + public QueryParameter getParameter(String name, Class type) { try { final QueryParameter parameter = getParameterMetadata().getQueryParameter( name ); if ( !parameter.getParameterType().isAssignableFrom( type ) ) { @@ -706,7 +704,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } @Override - public Parameter getParameter(int position) { + public QueryParameter getParameter(int position) { // It is important to understand that there are 2 completely distinct conceptualization of // "positional parameters" in play here: // 1) The legacy Hibernate concept is akin to JDBC PreparedStatement parameters. Very limited and @@ -739,7 +737,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @Override @SuppressWarnings("unchecked") - public Parameter getParameter(int position, Class type) { + public QueryParameter getParameter(int position, Class type) { try { final QueryParameter parameter = getParameterMetadata().getQueryParameter( position ); if ( !parameter.getParameterType().isAssignableFrom( type ) ) { @@ -759,18 +757,20 @@ public abstract class AbstractProducedQuery implements QueryImplementor { @Override public boolean isBound(Parameter parameter) { getProducer().checkOpen(); - return queryParameterBindings.isBound( (QueryParameter) parameter ); + return getQueryParameterBindings().isBound( (QueryParameter) parameter ); } @Override public T getParameterValue(Parameter parameter) { + LOGGER.tracef( "#getParameterValue(%s)", parameter ); + getProducer().checkOpen( false ); - if ( !parameterMetadata.containsReference( (QueryParameter) parameter ) ) { + if ( !getParameterMetadata().containsReference( (QueryParameter) parameter ) ) { throw new IllegalArgumentException( "Parameter reference [" + parameter + "] did not come from this query" ); } - final QueryParameterBinding binding = queryParameterBindings.getBinding( (QueryParameter) parameter ); + final QueryParameterBinding binding = getQueryParameterBindings().getBinding( (QueryParameter) parameter ); LOGGER.debugf( "Checking whether parameter reference [%s] is bound : %s", parameter, binding.isBound() ); if ( !binding.isBound() ) { throw new IllegalStateException( "Parameter value not yet bound : " + parameter.toString() ); @@ -784,7 +784,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { final QueryParameterBinding binding; try { - binding = queryParameterBindings.getBinding( name ); + binding = getQueryParameterBindings().getBinding( name ); } catch (QueryParameterException e) { throw new IllegalArgumentException( "Could not resolve parameter by name - " + name, e ); @@ -803,7 +803,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { final QueryParameterBinding binding; try { - binding = queryParameterBindings.getBinding( position ); + binding = getQueryParameterBindings().getBinding( position ); } catch (QueryParameterException e) { throw new IllegalArgumentException( "Could not resolve parameter by position - " + position, e ); @@ -849,7 +849,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } protected Type determineType(String namedParam, Class retType) { - Type type = queryParameterBindings.getBinding( namedParam ).getBindType(); + Type type = getQueryParameterBindings().getBinding( namedParam ).getBindType(); if ( type == null ) { type = getParameterMetadata().getQueryParameter( namedParam ).getType(); } @@ -1331,8 +1331,8 @@ public abstract class AbstractProducedQuery implements QueryImplementor { if ( cls.isInstance( getParameterMetadata() ) ) { return (T) getParameterMetadata(); } - if ( cls.isInstance( queryParameterBindings ) ) { - return (T) queryParameterBindings; + if ( cls.isInstance( getQueryParameterBindings() ) ) { + return (T) getQueryParameterBindings(); } if ( cls.isInstance( this ) ) { return (T) this; @@ -1358,7 +1358,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } QueryParameters queryParameters = new QueryParameters( - queryParameterBindings, + getQueryParameterBindings(), getLockOptions(), queryOptions, true, @@ -1381,23 +1381,23 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } public QueryParameters getQueryParameters() { - final String expandedQuery = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return makeQueryParametersForExecution( expandedQuery ); } @SuppressWarnings("deprecation") protected Type[] getPositionalParameterTypes() { - return queryParameterBindings.collectPositionalBindTypes(); + return getQueryParameterBindings().collectPositionalBindTypes(); } @SuppressWarnings("deprecation") protected Object[] getPositionalParameterValues() { - return queryParameterBindings.collectPositionalBindValues(); + return getQueryParameterBindings().collectPositionalBindValues(); } @SuppressWarnings("deprecation") protected Map getNamedParameterMap() { - return queryParameterBindings.collectNamedParameterBindings(); + return getQueryParameterBindings().collectNamedParameterBindings(); } private FlushMode sessionFlushMode; @@ -1405,7 +1405,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { protected void beforeQuery() { if ( optionalId == null ) { - queryParameterBindings.verifyParametersBound( isCallable() ); + getQueryParameterBindings().verifyParametersBound( isCallable() ); } assert sessionFlushMode == null; @@ -1450,7 +1450,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { return EmptyIterator.INSTANCE; } return getProducer().iterate( - queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ), + getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ), getQueryParameters() ); } @@ -1475,7 +1475,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { if (getMaxResults() == 0){ return EmptyScrollableResults.INSTANCE; } - final String query = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); + final String query = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); QueryParameters queryParameters = makeQueryParametersForExecution( query ); queryParameters.setScrollMode( scrollMode ); return getProducer().scroll( query, queryParameters ); @@ -1538,16 +1538,14 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } } - final String expandedQuery = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().list( expandedQuery, makeQueryParametersForExecution( expandedQuery ) ); } - public QueryParameterBindingsImpl getQueryParameterBindings() { - return queryParameterBindings; - } + protected abstract QueryParameterBindings getQueryParameterBindings(); @Override public R uniqueResult() { @@ -1620,7 +1618,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } protected int doExecuteUpdate() { - final String expandedQuery = queryParameterBindings.expandListValuedParameters( getQueryString(), getProducer() ); + final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return getProducer().executeUpdate( expandedQuery, makeQueryParametersForExecution( expandedQuery ) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java index d23a4663bd..fe41bc9edd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/CollectionFilterImpl.java @@ -14,6 +14,7 @@ import org.hibernate.ScrollMode; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.Query; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.type.Type; @@ -25,6 +26,7 @@ import org.hibernate.type.Type; public class CollectionFilterImpl extends org.hibernate.query.internal.AbstractProducedQuery { private final String queryString; private Object collection; + private final QueryParameterBindingsImpl queryParameterBindings; public CollectionFilterImpl( String queryString, @@ -34,6 +36,16 @@ public class CollectionFilterImpl extends org.hibernate.query.internal.AbstractP super( session, parameterMetadata ); this.queryString = queryString; this.collection = collection; + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java index eb3f38179c..9702b88db4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/NativeQueryImpl.java @@ -44,6 +44,7 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.NativeQueryImplementor; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -55,6 +56,7 @@ import static org.hibernate.jpa.QueryHints.HINT_NATIVE_LOCKMODE; */ public class NativeQueryImpl extends AbstractProducedQuery implements NativeQueryImplementor { private final String sqlString; + private final QueryParameterBindingsImpl queryParameterBindings; private List queryReturns; private List queryReturnBuilders; private boolean autoDiscoverTypes; @@ -100,6 +102,13 @@ public class NativeQueryImpl extends AbstractProducedQuery implements Nati else { this.queryReturns = new ArrayList<>(); } + + + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); } public NativeQueryImpl( @@ -113,6 +122,17 @@ public class NativeQueryImpl extends AbstractProducedQuery implements Nati this.sqlString = sqlString; this.callable = callable; this.querySpaces = new ArrayList<>(); + + this.queryParameterBindings = QueryParameterBindingsImpl.from( + sqlParameterMetadata, + session.getFactory(), + session.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java index 1104cc306a..7ef8c04e73 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import javax.persistence.Parameter; import org.hibernate.QueryException; @@ -197,14 +198,6 @@ public class ParameterMetadataImpl implements ParameterMetadata { return (QueryParameter) param; } - if ( param.getName() != null ) { - return getQueryParameter( param.getName() ); - } - - if ( param.getPosition() != null ) { - return getQueryParameter( param.getPosition() ); - } - throw new IllegalArgumentException( "Could not resolve javax.persistence.Parameter to org.hibernate.query.QueryParameter" ); } @@ -232,6 +225,20 @@ public class ParameterMetadataImpl implements ParameterMetadata { return descriptor; } + @Override + public void visitRegistrations(Consumer action) { + if ( hasPositionalParameters() ) { + for ( OrdinalParameterDescriptor descriptor : ordinalDescriptorMap.values() ) { + action.accept( descriptor ); + } + } + else if ( hasNamedParameters() ) { + for ( NamedParameterDescriptor descriptor : namedDescriptorMap.values() ) { + action.accept( descriptor ); + } + } + } + /** * Deprecated. * diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java index f084b241a5..cda7388a8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java @@ -9,6 +9,7 @@ package org.hibernate.query.internal; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.Query; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.type.Type; /** @@ -17,12 +18,24 @@ import org.hibernate.type.Type; public class QueryImpl extends AbstractProducedQuery implements Query { private final String queryString; + private final QueryParameterBindingsImpl queryParameterBindings; + public QueryImpl( SharedSessionContractImplementor producer, ParameterMetadata parameterMetadata, String queryString) { super( producer, parameterMetadata ); this.queryString = queryString; + this.queryParameterBindings = QueryParameterBindingsImpl.from( + parameterMetadata, + producer.getFactory(), + producer.isQueryParametersValidationEnabled() + ); + } + + @Override + protected QueryParameterBindings getQueryParameterBindings() { + return queryParameterBindings; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java index f4e47e3a82..d7f1144b58 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterImpl.java @@ -18,7 +18,7 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ public abstract class QueryParameterImpl implements QueryParameter { - private final Type expectedType; + private Type expectedType; public QueryParameterImpl(Type expectedType) { this.expectedType = expectedType; @@ -29,6 +29,10 @@ public abstract class QueryParameterImpl implements QueryParameter { return expectedType; } + public void setHibernateType(Type expectedType) { + this.expectedType = expectedType; + } + @Override public Class getParameterType() { return expectedType == null ? null : expectedType.getReturnedClass(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java new file mode 100644 index 0000000000..c2508c05c9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java @@ -0,0 +1,135 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.procedure.internal; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.ParameterMode; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; +import org.hibernate.procedure.ParameterBind; +import org.hibernate.procedure.internal.ParameterBindImpl; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.query.QueryParameter; +import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.spi.QueryParameterListBinding; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class ProcedureParamBindings implements QueryParameterBindings { + private final ProcedureParameterMetadata parameterMetadata; + private final ProcedureCallImpl procedureCall; + + private final Map bindingMap = new HashMap<>(); + + public ProcedureParamBindings( + ProcedureParameterMetadata parameterMetadata, + ProcedureCallImpl procedureCall) { + this.parameterMetadata = parameterMetadata; + this.procedureCall = procedureCall; + } + + public ProcedureParameterMetadata getParameterMetadata() { + return parameterMetadata; + } + + public ProcedureCallImpl getProcedureCall() { + return procedureCall; + } + + @Override + public boolean isBound(QueryParameter parameter) { + return getBinding( parameter ).isBound(); + } + + @Override + public QueryParameterBinding getBinding(QueryParameter parameter) { + final ProcedureParameterImplementor procParam = parameterMetadata.resolve( parameter ); + ParameterBind binding = bindingMap.get( procParam ); + + if ( binding == null ) { + if ( ! parameterMetadata.containsReference( parameter ) ) { + throw new IllegalArgumentException( "Passed parameter is not registered with this query" ); + } + + binding = new ParameterBindImpl( procParam, this ); + bindingMap.put( procParam, binding ); + } + + return binding; + } + + @Override + public QueryParameterBinding getBinding(String name) { + return getBinding( parameterMetadata.getQueryParameter( name ) ); + } + + @Override + public QueryParameterBinding getBinding(int position) { + return getBinding( parameterMetadata.getQueryParameter( position ) ); + } + + @Override + public void verifyParametersBound(boolean callable) { + parameterMetadata.visitRegistrations( + queryParameter -> { + final ProcedureParameterImplementor procParam = (ProcedureParameterImplementor) queryParameter; + if ( procParam.getMode() == ParameterMode.IN + || procParam.getMode() == ParameterMode.INOUT ) { + if ( !getBinding( procParam ).isBound() ) { + // depending on "pass nulls" this might be ok... + // for now, just log a warning + } + } + } + ); + } + + @Override + public String expandListValuedParameters(String queryString, SharedSessionContractImplementor producer) { + return queryString; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(QueryParameter parameter) { + return null; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(String name) { + return null; + } + + @Override + public QueryParameterListBinding getQueryParameterListBinding(int position) { + return null; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // I think these are not needed for proc call execution + + @Override + public Type[] collectPositionalBindTypes() { + return new Type[0]; + } + + @Override + public Object[] collectPositionalBindValues() { + return new Object[0]; + } + + @Override + public Map collectNamedParameterBindings() { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java index 701048ce91..a82ff7ec07 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java @@ -6,36 +6,101 @@ */ package org.hibernate.query.procedure.internal; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Calendar; import javax.persistence.ParameterMode; +import javax.persistence.TemporalType; -import org.hibernate.procedure.spi.ParameterRegistrationImplementor; +import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; +import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.procedure.ParameterBind; +import org.hibernate.procedure.ParameterMisuseException; +import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.query.internal.QueryParameterImpl; import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.type.CalendarDateType; +import org.hibernate.type.CalendarTimeType; +import org.hibernate.type.CalendarType; +import org.hibernate.type.ProcedureParameterExtractionAware; +import org.hibernate.type.ProcedureParameterNamedBinder; +import org.hibernate.type.Type; + +import org.jboss.logging.Logger; /** * @author Steve Ebersole */ -public class ProcedureParameterImpl extends QueryParameterImpl implements ProcedureParameterImplementor { - private ParameterRegistrationImplementor nativeParamRegistration; +public class ProcedureParameterImpl + extends QueryParameterImpl + implements ProcedureParameterImplementor, ParameterRegistration { + private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class ); - public ProcedureParameterImpl(ParameterRegistrationImplementor nativeParamRegistration) { - super( nativeParamRegistration.getHibernateType() ); - this.nativeParamRegistration = nativeParamRegistration; + private final ProcedureCallImpl procedureCall; + private final String name; + private final Integer position; + private final ParameterMode mode; + private final Class javaType; + + private int[] sqlTypes; + private boolean passNullsEnabled; + + // in-flight state needed between prepare and extract + private int startIndex; + + public ProcedureParameterImpl( + ProcedureCallImpl procedureCall, + String name, + ParameterMode mode, + Class javaType, + Type hibernateType, + boolean initialPassNullsSetting) { + super( hibernateType ); + this.procedureCall = procedureCall; + this.name = name; + this.position = null; + this.mode = mode; + this.javaType = javaType; + this.passNullsEnabled = initialPassNullsSetting; + + setHibernateType( hibernateType ); + } + + public ProcedureParameterImpl( + ProcedureCallImpl procedureCall, + Integer position, + ParameterMode mode, + Class javaType, + Type hibernateType, + boolean initialPassNullsSetting) { + super( hibernateType ); + this.procedureCall = procedureCall; + this.name = null; + this.position = position; + this.mode = mode; + this.javaType = javaType; + this.passNullsEnabled = initialPassNullsSetting; + + setHibernateType( hibernateType ); } @Override public ParameterMode getMode() { - return nativeParamRegistration.getMode(); + return mode; } @Override public boolean isPassNullsEnabled() { - return nativeParamRegistration.isPassNullsEnabled(); + return passNullsEnabled; } @Override public void enablePassingNulls(boolean enabled) { - nativeParamRegistration.enablePassingNulls( enabled ); + this.passNullsEnabled = enabled; } @Override @@ -45,16 +110,287 @@ public class ProcedureParameterImpl extends QueryParameterImpl implements @Override public String getName() { - return nativeParamRegistration.getName(); + return name; } @Override public Integer getPosition() { - return nativeParamRegistration.getPosition(); + return position; } @Override - public ParameterRegistrationImplementor getNativeParameterRegistration() { - return nativeParamRegistration; + public Type getHibernateType() { + return getType(); + } + + @Override + public void setHibernateType(Type expectedType) { + super.setHibernateType( expectedType ); + + if ( mode == ParameterMode.REF_CURSOR ) { + sqlTypes = new int[] { Types.REF_CURSOR }; + } + else { + if ( expectedType == null ) { + throw new IllegalArgumentException( "Type cannot be null" ); + } + else { + sqlTypes = expectedType.sqlTypes( procedureCall.getSession().getFactory() ); + } + } + + } + + @Override + public Class getParameterType() { + return javaType; + } + + @Override + public ParameterBind getBind() { + return (ParameterBind) procedureCall.getQueryParameterBindings().getBinding( this ); + } + + @Override + @SuppressWarnings("unchecked") + public void bindValue(Object value) { + getBind().setBindValue( (T) value ); + } + + @Override + @SuppressWarnings("unchecked") + public void bindValue(Object value, TemporalType explicitTemporalType) { + getBind().setBindValue( (T) value, explicitTemporalType ); + } + + @Override + public void prepare(CallableStatement statement, int startIndex) throws SQLException { + // initially set up the Type we will use for binding as the explicit type. + Type typeToUse = getHibernateType(); + int[] sqlTypesToUse = sqlTypes; + + final ParameterBind bind = getBind(); + + // however, for Calendar binding with an explicit TemporalType we may need to adjust this... + if ( bind != null && bind.getExplicitTemporalType() != null ) { + if ( Calendar.class.isInstance( bind.getValue() ) ) { + switch ( bind.getExplicitTemporalType() ) { + case TIMESTAMP: { + typeToUse = CalendarType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + case DATE: { + typeToUse = CalendarDateType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + case TIME: { + typeToUse = CalendarTimeType.INSTANCE; + sqlTypesToUse = typeToUse.sqlTypes( procedureCall.getSession().getFactory() ); + break; + } + } + } + } + + this.startIndex = startIndex; + if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + 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. + if ( sqlTypesToUse.length == 1 && + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && + canDoNameParameterBinding( typeToUse ) ) { + statement.registerOutParameter( getName(), sqlTypesToUse[0] ); + } + else { + for ( int i = 0; i < sqlTypesToUse.length; i++ ) { + statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); + } + } + } + + if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { + if ( bind == null || bind.getValue() == null ) { + // the user did not bind 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 bind 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 ( isPassNullsEnabled() ) { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", + procedureCall.getProcedureName(), + this + ); + if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + null, + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, null, startIndex, procedureCall.getSession() ); + } + } + else { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", + procedureCall.getProcedureName(), + this + ); + } + } + else { + if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + bind.getValue(), + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, procedureCall.getSession() ); + } + } + } + } + else { + // we have a REF_CURSOR type param + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, getName() ); + } + else { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, startIndex ); + } + } + } + + private boolean canDoNameParameterBinding(Type hibernateType) { + final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() + .getJdbcCoordinator() + .getJdbcSessionOwner() + .getJdbcSessionContext() + .getServiceRegistry().getService( JdbcEnvironment.class ) + .getExtractedDatabaseMetaData(); + return + databaseMetaData.supportsNamedParameters() + && ProcedureParameterNamedBinder.class.isInstance( hibernateType ) + && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); + } + + public int[] getSqlTypes() { + if ( mode == ParameterMode.REF_CURSOR ) { + // we could use the Types#REF_CURSOR added in Java 8, but that would require requiring Java 8... + throw new IllegalStateException( "REF_CURSOR parameters do not have a SQL/JDBC type" ); + } + + return determineHibernateType().sqlTypes( procedureCall.getSession().getFactory() ); + } + + private Type determineHibernateType() { + final ParameterBind bind = getBind(); + + // if the bind defines a type, that should be the most specific... + final Type bindType = bind.getBindType(); + if ( bindType != null ) { + return bindType; + } + + // Next, see if the parameter itself has an expected type, and if so use that... + final Type paramType = getHibernateType(); + if ( paramType != null ) { + return paramType; + } + + // here we just have guessing games + if ( bind.getValue() != null ) { + return procedureCall.getSession() + .getFactory() + .getTypeResolver() + .heuristicType( bind.getValue().getClass().getName() ); + } + + throw new IllegalStateException( "Unable to determine SQL type(s) - Hibernate Type not known" ); + } + + @Override + @SuppressWarnings("unchecked") + public T extract(CallableStatement statement) { + if ( mode == ParameterMode.IN ) { + throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); + } + + final Type hibernateType = determineHibernateType(); + final int[] sqlTypes = hibernateType.sqlTypes( procedureCall.getSession().getFactory() ); + + // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). + // For now, if sqlTypes.length > 1 with a named parameter, then extract + // parameter values by position (since we only have one name). + final boolean useNamed = sqlTypes.length == 1 && + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && + canDoNameParameterBinding( hibernateType ); + + try { + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { + if ( useNamed ) { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( + statement, + new String[] { getName() }, + procedureCall.getSession() + ); + } + else { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( + statement, + startIndex, + procedureCall.getSession() + ); + } + } + else { + if ( useNamed ) { + return (T) statement.getObject( name ); + } + else { + return (T) statement.getObject( startIndex ); + } + } + } + catch (SQLException e) { + throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( + e, + "Unable to extract OUT/INOUT parameter value" + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java index 344cb3d721..21cbe7f08b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadata.java @@ -12,10 +12,13 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.persistence.Parameter; -import org.hibernate.QueryParameterException; +import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.procedure.spi.ParameterRegistrationImplementor; +import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.QueryParameter; import org.hibernate.query.procedure.ProcedureParameter; @@ -25,35 +28,46 @@ import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; * @author Steve Ebersole */ public class ProcedureParameterMetadata implements ParameterMetadata { - private List parameters; + private final ProcedureCallImpl procedureCall; + private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; + private List parameters = new ArrayList<>(); - private boolean hasNamed; - private int ordinalParamCount; - - public ProcedureParameterMetadata() { - parameters = new ArrayList<>( ); + public ProcedureParameterMetadata(ProcedureCallImpl procedureCall) { + this.procedureCall = procedureCall; } public void registerParameter(ProcedureParameterImplementor parameter) { + if ( parameter.getName() != null ) { + if ( parameterStrategy == ParameterStrategy.POSITIONAL ) { + throw new IllegalArgumentException( "Cannot mix named parameter with positional parameter registrations" ); + } + parameterStrategy = ParameterStrategy.NAMED; + } + else if ( parameter.getPosition() != null ) { + if ( parameterStrategy == ParameterStrategy.NAMED ) { + throw new IllegalArgumentException( "Cannot mix positional parameter with named parameter registrations" ); + } + this.parameterStrategy = ParameterStrategy.POSITIONAL; + } + else { + throw new IllegalArgumentException( "Unrecognized parameter type : " + parameter ); + } + + if ( parameters == null ) { parameters = new ArrayList<>(); } parameters.add( parameter ); - - this.hasNamed = hasNamed || parameter.getName() != null; - if ( parameter.getPosition() != null ) { - ordinalParamCount++; - } } @Override public boolean hasNamedParameters() { - return hasNamed; + return parameterStrategy == ParameterStrategy.NAMED; } @Override public boolean hasPositionalParameters() { - return ordinalParamCount > 0; + return parameterStrategy == ParameterStrategy.POSITIONAL; } @Override @@ -76,7 +90,7 @@ public class ProcedureParameterMetadata implements ParameterMetadata { @Override public Set getNamedParameterNames() { - if ( !hasNamed ) { + if ( !hasNamedParameters() ) { return Collections.emptySet(); } @@ -91,69 +105,53 @@ public class ProcedureParameterMetadata implements ParameterMetadata { @Override public int getPositionalParameterCount() { - return ordinalParamCount; + return hasPositionalParameters() ? parameters.size() : 0; } @Override @SuppressWarnings("unchecked") - public QueryParameter getQueryParameter(String name) { + public ParameterRegistrationImplementor getQueryParameter(String name) { assert name != null; - QueryParameter result = null; - if ( hasNamed ) { - for ( ProcedureParameter parameter : parameters ) { + + if ( hasNamedParameters() ) { + for ( ParameterRegistrationImplementor parameter : parameters ) { if ( name.equals( parameter.getName() ) ) { - result = parameter; - break; + return parameter; } } } - if ( result != null ) { - return result; - } - throw new QueryParameterException( "could not locate named parameter [" + name + "]" ); + + throw new IllegalArgumentException( "Named parameter [" + name + "] is not registered with this procedure call" ); } @Override @SuppressWarnings("unchecked") - public QueryParameter getQueryParameter(Integer position) { + public ParameterRegistrationImplementor getQueryParameter(Integer position) { assert position != null; - if ( ordinalParamCount > 0 ) { - for ( ProcedureParameter parameter : parameters ) { + if ( hasPositionalParameters() ) { + for ( ParameterRegistrationImplementor parameter : parameters ) { if ( parameter.getPosition() != null && position.intValue() == parameter.getPosition() ) { return parameter; } } } - throw new QueryParameterException( "could not locate parameter at position [" + position + "]" ); + + throw new IllegalArgumentException( "Positional parameter [" + position + "] is not registered with this procedure call" ); } @Override @SuppressWarnings("unchecked") - public QueryParameter resolve(Parameter param) { - // first see if that instance exists here... - for ( ProcedureParameter parameter : parameters ) { - if ( parameter == param ) { - return parameter; - } - } - - // otherwise, try name/position from the incoming param - if ( param.getPosition() != null || param.getName() != null ) { - for ( ProcedureParameter parameter : parameters ) { - // name - if ( param.getName() != null && param.getName().equals( parameter.getName() ) ) { - return parameter; - } - - // position - if ( param.getPosition() != null && param.getPosition().equals( parameter.getPosition() ) ) { + public ProcedureParameterImplementor resolve(Parameter param) { + if ( ProcedureParameterImplementor.class.isInstance( param ) ) { + for ( ProcedureParameterImplementor parameter : parameters ) { + if ( parameter == param ) { return parameter; } } } - return null; + throw new IllegalArgumentException( "Could not resolve javax.persistence.Parameter to org.hibernate.query.QueryParameter" ); } @Override @@ -176,4 +174,16 @@ public class ProcedureParameterMetadata implements ParameterMetadata { public boolean containsReference(QueryParameter parameter) { return parameters.contains( parameter ); } + + public ParameterStrategy getParameterStrategy() { + return parameterStrategy; + } + + @Override + public void visitRegistrations(Consumer action) { + for ( ProcedureParameterImplementor parameter : parameters ) { + action.accept( parameter ); + } + + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java index 3f11d5edf5..f671e05a2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java @@ -12,6 +12,5 @@ import org.hibernate.query.procedure.ProcedureParameter; /** * @author Steve Ebersole */ -public interface ProcedureParameterImplementor extends ProcedureParameter { - ParameterRegistrationImplementor getNativeParameterRegistration(); +public interface ProcedureParameterImplementor extends ProcedureParameter, ParameterRegistrationImplementor { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java index 78d89ef5d6..5eb46e9b26 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindings.java @@ -6,8 +6,13 @@ */ package org.hibernate.query.spi; +import java.util.Map; + import org.hibernate.Incubating; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.TypedValue; import org.hibernate.query.QueryParameter; +import org.hibernate.type.Type; /** * @author Steve Ebersole @@ -19,4 +24,15 @@ public interface QueryParameterBindings { QueryParameterBinding getBinding(QueryParameter parameter); QueryParameterBinding getBinding(String name); QueryParameterBinding getBinding(int position); + + void verifyParametersBound(boolean callable); + String expandListValuedParameters(String queryString, SharedSessionContractImplementor producer); + + QueryParameterListBinding getQueryParameterListBinding(QueryParameter parameter); + QueryParameterListBinding getQueryParameterListBinding(String name); + QueryParameterListBinding getQueryParameterListBinding(int position); + + Type[] collectPositionalBindTypes(); + Object[] collectPositionalBindValues(); + Map collectNamedParameterBindings(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/StoredProcedureApiTests.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/StoredProcedureApiTests.java new file mode 100644 index 0000000000..7b71356b3a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/StoredProcedureApiTests.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.jpa.compliance.tck2_2; + +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Parameter; +import javax.persistence.ParameterMode; +import javax.persistence.Table; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.query.QueryParameter; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.sql.storedproc.StoredProcedureResultSetMappingTest; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * @author Steve Ebersole + */ +@RequiresDialect( H2Dialect.class ) +public class StoredProcedureApiTests extends BaseNonConfigCoreFunctionalTestCase { + + + @Test + public void parameterValueAccess() { + inTransaction( + session -> { + final ProcedureCall call = session.createStoredProcedureCall( "test" ); + + call.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN); + call.registerStoredProcedureParameter( 2, String.class, ParameterMode.OUT); + call.setParameter( 1, 1 ); + call.getParameterValue( 1 ); + } + ); + } + + @Test + public void testInvalidParameterReference() { + inTransaction( + session -> { + final ProcedureCall call1 = session.createStoredProcedureCall( "test" ); + call1.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN); + final Parameter p1_1 = (Parameter) call1.getParameter( 1 ); + call1.setParameter( 1, 1 ); + + final ProcedureCall call2 = session.createStoredProcedureCall( "test" ); + call2.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN); + call2.setParameter( 1, 1 ); + + try { + call2.getParameterValue( p1_1 ); + fail( "Expecting failure" ); + } + catch (IllegalArgumentException expected) { + + } + } + ); + } + + @Test + public void testParameterBindTypeMismatch() { + inTransaction( + session -> { + try { + final ProcedureCall call1 = session.createStoredProcedureCall( "test" ); + call1.registerStoredProcedureParameter( 1, Integer.class, ParameterMode.IN ); + call1.setParameter( 1, new Date() ); + + fail( "expecting failure" ); + } + catch (IllegalArgumentException expected) { + } + } + ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + + sources.addAnnotatedClass( Person.class ); + } + + @Override + protected void afterMetadataBuilt(Metadata metadata) { + super.afterMetadataBuilt( metadata ); + +// metadata.getDatabase().addAuxiliaryDatabaseObject( +// new StoredProcedureResultSetMappingTest.ProcedureDefinition() { +// @Override +// public boolean appliesToDialect(Dialect dialect) { +// return H2Dialect.class.isInstance( dialect ); +// } +// +// @Override +// public boolean beforeTablesOnCreation() { +// return false; +// } +// +// @Override +// public String getExportIdentifier() { +// return "StoredProcedure#test" +// } +// +// @Override +// public String[] sqlCreateStrings(Dialect dialect) { +// return super.sqlCreateStrings( dialect ); +// } +// +// @Override +// public String[] sqlDropStrings(Dialect dialect) { +// return super.sqlDropStrings( dialect ); +// } +// } +// ); + } + + @Entity( name = "Person" ) + @Table( name = "person" ) + public static class Person { + @Id + public Integer id; + public String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java index 39470d8e3f..0be6cd595d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java @@ -284,8 +284,10 @@ public class StoredProcedureTest extends BaseCoreFunctionalTestCase { session.beginTransaction(); ProcedureCall query = session.createStoredProcedureCall( "findUserRange" ); - query.registerParameter( 1, Integer.class, ParameterMode.IN ).bindValue( 1 ); - query.registerParameter( 2, Integer.class, ParameterMode.IN ).bindValue( 2 ); + query.registerParameter( 1, Integer.class, ParameterMode.IN ) + .bindValue( 1 ); + query.registerParameter( 2, Integer.class, ParameterMode.IN ) + .bindValue( 2 ); ProcedureOutputs procedureResult = query.getOutputs(); Output currentOutput = procedureResult.getCurrent(); assertNotNull( currentOutput );