HHH-16917 - Number not allowed as type for procedure query parameter
This commit is contained in:
parent
8031952d86
commit
8386e1851e
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.procedure;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
/**
|
||||
* Indicates Hibernate is unable to determine the type details for a parameter.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ParameterTypeException extends HibernateException {
|
||||
public ParameterTypeException(String message) {
|
||||
super( message );
|
||||
}
|
||||
}
|
|
@ -9,10 +9,12 @@ package org.hibernate.procedure.internal;
|
|||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.procedure.ParameterTypeException;
|
||||
import org.hibernate.procedure.spi.NamedCallableQueryMemento;
|
||||
import org.hibernate.procedure.spi.ParameterStrategy;
|
||||
import org.hibernate.procedure.spi.ProcedureCallImplementor;
|
||||
|
@ -44,6 +46,9 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
|
|||
private final ParameterMode mode;
|
||||
private final Class<T> javaType;
|
||||
|
||||
/**
|
||||
* Used for named Query parameters
|
||||
*/
|
||||
public ProcedureParameterImpl(
|
||||
String name,
|
||||
ParameterMode mode,
|
||||
|
@ -56,6 +61,9 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
|
|||
this.javaType = javaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for ordinal Query parameters
|
||||
*/
|
||||
public ProcedureParameterImpl(
|
||||
Integer position,
|
||||
ParameterMode mode,
|
||||
|
@ -115,21 +123,32 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
|
|||
int startIndex,
|
||||
ProcedureCallImplementor<?> procedureCall) {
|
||||
final QueryParameterBinding<T> binding = procedureCall.getParameterBindings().getBinding( this );
|
||||
final boolean isNamed = procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && this.name != null;
|
||||
|
||||
final BindableType<T> bindableType;
|
||||
if ( getHibernateType() != null ) {
|
||||
bindableType = getHibernateType();
|
||||
}
|
||||
else if ( binding != null ) {
|
||||
//noinspection unchecked
|
||||
bindableType = (BindableType<T>) binding.getBindType();
|
||||
}
|
||||
else {
|
||||
bindableType = null;
|
||||
}
|
||||
|
||||
final OutputableType<T> typeToUse = (OutputableType<T>) BindingTypeHelper.INSTANCE.resolveTemporalPrecision(
|
||||
binding == null || binding.getExplicitTemporalPrecision() == null
|
||||
? null
|
||||
: binding.getExplicitTemporalPrecision(),
|
||||
getHibernateType(),
|
||||
binding == null ? null : binding.getExplicitTemporalPrecision(),
|
||||
bindableType,
|
||||
procedureCall.getSession().getFactory()
|
||||
);
|
||||
|
||||
final String name;
|
||||
if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED
|
||||
&& canDoNameParameterBinding( typeToUse, procedureCall ) ) {
|
||||
name = this.name;
|
||||
final String jdbcParamName;
|
||||
if ( isNamed && canDoNameParameterBinding( typeToUse, procedureCall ) ) {
|
||||
jdbcParamName = this.name;
|
||||
}
|
||||
else {
|
||||
name = null;
|
||||
jdbcParamName = null;
|
||||
}
|
||||
|
||||
final JdbcParameterBinder parameterBinder;
|
||||
|
@ -138,31 +157,58 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
|
|||
|
||||
switch ( mode ) {
|
||||
case REF_CURSOR:
|
||||
refCursorExtractor = new JdbcCallRefCursorExtractorImpl( name, startIndex );
|
||||
refCursorExtractor = new JdbcCallRefCursorExtractorImpl( jdbcParamName, startIndex );
|
||||
parameterBinder = null;
|
||||
parameterExtractor = null;
|
||||
break;
|
||||
case IN:
|
||||
parameterBinder = getParameterBinder( typeToUse, name );
|
||||
validateBindableType( typeToUse, startIndex );
|
||||
parameterBinder = getParameterBinder( typeToUse, jdbcParamName );
|
||||
parameterExtractor = null;
|
||||
refCursorExtractor = null;
|
||||
break;
|
||||
case INOUT:
|
||||
parameterBinder = getParameterBinder( typeToUse, name );
|
||||
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), name, startIndex, typeToUse );
|
||||
validateBindableType( typeToUse, startIndex );
|
||||
parameterBinder = getParameterBinder( typeToUse, jdbcParamName );
|
||||
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse );
|
||||
refCursorExtractor = null;
|
||||
break;
|
||||
default:
|
||||
validateBindableType( typeToUse, startIndex );
|
||||
parameterBinder = null;
|
||||
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), name, startIndex, typeToUse );
|
||||
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse );
|
||||
refCursorExtractor = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return new JdbcCallParameterRegistrationImpl( name, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor );
|
||||
return new JdbcCallParameterRegistrationImpl( jdbcParamName, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor );
|
||||
}
|
||||
|
||||
private void validateBindableType(BindableType<T> bindableType, int startIndex) {
|
||||
if ( bindableType == null ) {
|
||||
throw new ParameterTypeException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Could not determine ProcedureCall parameter bind type - %s (%s)",
|
||||
this.name != null ? this.name : this.position,
|
||||
startIndex
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private JdbcParameterBinder getParameterBinder(BindableType<T> typeToUse, String name) {
|
||||
if ( typeToUse == null ) {
|
||||
throw new ParameterTypeException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Cannot determine the bindable type for procedure parameter %s (%s)",
|
||||
this.name != null ? this.name : this.position,
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( typeToUse instanceof BasicType<?> ) {
|
||||
if ( name == null ) {
|
||||
return new JdbcParameterImpl( (BasicType<T>) typeToUse );
|
||||
|
@ -190,12 +236,8 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
|
|||
};
|
||||
}
|
||||
}
|
||||
else if ( typeToUse == null ) {
|
||||
throw new IllegalArgumentException( "Cannot determine the bindable type for procedure parameter: " + name );
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private boolean canDoNameParameterBinding(
|
||||
|
|
|
@ -11,8 +11,6 @@ import java.sql.SQLException;
|
|||
|
||||
import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.query.OutputableType;
|
||||
import org.hibernate.sql.exec.spi.JdbcCallParameterExtractor;
|
||||
import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.orm.test.procedure;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class Helper {
|
||||
@FunctionalInterface
|
||||
public interface StatementAction {
|
||||
void accept(Statement statement) throws SQLException;
|
||||
}
|
||||
|
||||
public static void withStatement(SessionFactoryImplementor sessionFactory, StatementAction action) throws SQLException {
|
||||
final JdbcConnectionAccess connectionAccess = sessionFactory.getServiceRegistry()
|
||||
.getService( JdbcServices.class )
|
||||
.getBootstrapJdbcConnectionAccess();
|
||||
|
||||
try (Connection connection = connectionAccess.obtainConnection()) {
|
||||
withStatement( connection, action );
|
||||
}
|
||||
}
|
||||
|
||||
public static void withStatement(Connection connection, StatementAction action) throws SQLException {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
action.accept( statement );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.orm.test.procedure;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.procedure.ParameterTypeException;
|
||||
import org.hibernate.procedure.ProcedureCall;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.ParameterMode;
|
||||
import jakarta.persistence.StoredProcedureQuery;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.type.descriptor.java.CoercionHelper.toShort;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Test for using unsupported Java type ({@link Number} e.g.) as a parameter type
|
||||
* for {@link ProcedureCall} / {@link StoredProcedureQuery} query parameters
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@RequiresDialect( HSQLDialect.class )
|
||||
@DomainModel
|
||||
@SessionFactory
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-16917" )
|
||||
public class NumericParameterTypeTests {
|
||||
|
||||
@Test
|
||||
void testParameterBaseline(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
executeQuery( Integer.class, Integer.class, 1, 2, session );
|
||||
} );
|
||||
}
|
||||
|
||||
private void executeQuery(
|
||||
Class<? extends Number> inArgType,
|
||||
Class<? extends Number> outArgType,
|
||||
Object inArgValue,
|
||||
Object expectedOutArgValue,
|
||||
SessionImplementor session) {
|
||||
final StoredProcedureQuery query = session.createStoredProcedureQuery( "inoutproc" );
|
||||
query.registerStoredProcedureParameter( "inarg", inArgType, ParameterMode.IN );
|
||||
query.registerStoredProcedureParameter( "outarg", outArgType, ParameterMode.OUT );
|
||||
query.setParameter( "inarg", inArgValue );
|
||||
final Object result = query.getOutputParameterValue( "outarg" );
|
||||
assertThat( result ).isEqualTo( expectedOutArgValue );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInputParameter(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
// Number is fine for IN parameters because we can ultimately look at the bind value type
|
||||
executeQuery( Number.class, Integer.class, 1, 2, session );
|
||||
|
||||
// in addition to Integer/Integer, we can also have IN parameters defined as numerous "implicit conversion" types
|
||||
executeQuery( Short.class, Integer.class, 1, 2, session );
|
||||
executeQuery( BigInteger.class, Integer.class, BigInteger.ONE, 2, session );
|
||||
executeQuery( Double.class, Integer.class, 1.0, 2, session );
|
||||
executeQuery( BigDecimal.class, Integer.class, BigDecimal.ONE, 2, session );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOutputParameter(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
try {
|
||||
// Number is not fine for OUT parameters
|
||||
executeQuery( Integer.class, Number.class, 1, 2, session );
|
||||
fail( "Expected a ParameterTypeException" );
|
||||
}
|
||||
catch (ParameterTypeException expected) {
|
||||
assertThat( expected.getMessage() ).contains( "outarg" );
|
||||
}
|
||||
|
||||
// in addition to Integer/Integer, we can also have OUT parameters defined as numerous "implicit conversion" types
|
||||
executeQuery( Integer.class, Short.class, 1, toShort( 2 ), session );
|
||||
executeQuery( Integer.class, BigInteger.class, BigDecimal.ONE, BigInteger.valueOf( 2 ), session );
|
||||
executeQuery( Integer.class, Double.class, 1, 2.0, session );
|
||||
executeQuery( Integer.class, BigDecimal.class, 1, BigDecimal.valueOf( 2 ), session );
|
||||
} );
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
void createProcedures(SessionFactoryScope scope) throws SQLException {
|
||||
final String procedureStatement = "create procedure inoutproc (IN inarg numeric, OUT outarg numeric) " +
|
||||
"begin atomic set outarg = inarg + 1;" +
|
||||
"end";
|
||||
|
||||
Helper.withStatement( scope.getSessionFactory(), statement -> {
|
||||
statement.execute( procedureStatement );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void dropProcedures(SessionFactoryScope scope) throws SQLException {
|
||||
Helper.withStatement( scope.getSessionFactory(), statement -> {
|
||||
statement.execute( "drop procedure inoutproc" );
|
||||
} );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue