Fix parameter binding validation
This commit is contained in:
parent
4d024fde8b
commit
6fcbe5f0a0
|
@ -21,10 +21,16 @@ import org.hibernate.query.spi.QueryParameterBinding;
|
|||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import jakarta.persistence.ParameterMode;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ProcedureParamBindings implements QueryParameterBindings {
|
||||
private static final Logger LOG = Logger.getLogger( QueryParameterBindings.class );
|
||||
|
||||
private final ProcedureParameterMetadataImpl parameterMetadata;
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
|
||||
|
@ -91,19 +97,28 @@ public class ProcedureParamBindings implements QueryParameterBindings {
|
|||
|
||||
@Override
|
||||
public void validate() {
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
throw new NotYetImplementedFor6Exception( getClass() );
|
||||
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
|
||||
if ( procParam.getPosition() != null ) {
|
||||
LOG.debugf(
|
||||
"Procedure parameter at position %s is not bound",
|
||||
procParam.getPosition()
|
||||
);
|
||||
|
||||
}
|
||||
else {
|
||||
LOG.debugf( "Procedure parameter %s is not bound", procParam.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -52,11 +52,11 @@ public class QueryParameterBindingValidator {
|
|||
}
|
||||
|
||||
final Class<?> parameterJavaType;
|
||||
final SqmExpressible<?> sqmExpressible = paramType.resolveExpressible( sessionFactory );
|
||||
if ( paramType.getBindableJavaType() != null ) {
|
||||
parameterJavaType = paramType.getBindableJavaType();
|
||||
}
|
||||
else {
|
||||
final SqmExpressible<?> sqmExpressible = paramType.resolveExpressible( sessionFactory );
|
||||
parameterJavaType = sqmExpressible.getBindableJavaType();
|
||||
}
|
||||
|
||||
|
@ -70,13 +70,26 @@ public class QueryParameterBindingValidator {
|
|||
// NOTE : this can happen in Hibernate's notion of "parameter list" binding
|
||||
// NOTE2 : the case of a collection value and an expected collection (if that can even happen)
|
||||
// will fall through to the main check.
|
||||
validateCollectionValuedParameterBinding( parameterJavaType, (Collection<?>) bind, temporalPrecision );
|
||||
validateCollectionValuedParameterBinding(
|
||||
parameterJavaType,
|
||||
(Collection<?>) bind,
|
||||
temporalPrecision
|
||||
);
|
||||
}
|
||||
else if ( bind.getClass().isArray() ) {
|
||||
validateArrayValuedParameterBinding( parameterJavaType, bind, temporalPrecision );
|
||||
validateArrayValuedParameterBinding(
|
||||
parameterJavaType,
|
||||
bind,
|
||||
temporalPrecision
|
||||
);
|
||||
}
|
||||
else {
|
||||
if ( !isValidBindValue( parameterJavaType, bind, temporalPrecision ) ) {
|
||||
if ( !isValidBindValue(
|
||||
sqmExpressible.getExpressibleJavaType(),
|
||||
parameterJavaType,
|
||||
bind,
|
||||
temporalPrecision
|
||||
) ) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Argument [%s] of type [%s] did not match parameter type [%s (%s)]",
|
||||
|
@ -113,7 +126,33 @@ public class QueryParameterBindingValidator {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean isValidBindValue(Class<?> expectedType, Object value, TemporalType temporalType) {
|
||||
private static boolean isValidBindValue(
|
||||
JavaType<?> expectedJavaType,
|
||||
Class<?> expectedType,
|
||||
Object value,
|
||||
TemporalType temporalType) {
|
||||
if ( value == null ) {
|
||||
return true;
|
||||
}
|
||||
else if ( expectedJavaType.isInstance( value ) ) {
|
||||
return true;
|
||||
}
|
||||
else if ( temporalType != null ) {
|
||||
final boolean parameterDeclarationIsTemporal = Date.class.isAssignableFrom( expectedType )
|
||||
|| Calendar.class.isAssignableFrom( expectedType );
|
||||
final boolean bindIsTemporal = value instanceof Date
|
||||
|| value instanceof Calendar;
|
||||
|
||||
return parameterDeclarationIsTemporal && bindIsTemporal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isValidBindValue(
|
||||
Class<?> expectedType,
|
||||
Object value,
|
||||
TemporalType temporalType) {
|
||||
if ( expectedType.isPrimitive() ) {
|
||||
if ( expectedType == boolean.class ) {
|
||||
return value instanceof Boolean;
|
||||
|
|
|
@ -205,6 +205,11 @@ public class JdbcTimestampJavaType extends AbstractTemporalJavaType<Date> implem
|
|||
return (TemporalJavaType<X>) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <X> TemporalJavaType<X> forDatePrecision(TypeConfiguration typeConfiguration) {
|
||||
return (TemporalJavaType<X>) JdbcDateJavaType.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
|
||||
return dialect.getDefaultTimestampPrecision();
|
||||
|
|
|
@ -1,36 +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 http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.jpa.compliance;
|
||||
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Jpa
|
||||
public class StoreProcedureTest {
|
||||
|
||||
@Test
|
||||
public void createNotExistingStoredProcedureQuery(EntityManagerFactoryScope scope) {
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
entityManager.createStoredProcedureQuery(
|
||||
"NOT_EXISTING_NAME",
|
||||
"NOT_EXISTING_RESULT_MAPPING"
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* 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.jpa.compliance;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.DerbyDialect;
|
||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ParameterMode;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.StoredProcedureQuery;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Temporal;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@Jpa(
|
||||
annotatedClasses = StoredProcedureTest.Person.class,
|
||||
properties = @Setting(name = AvailableSettings.JPA_LOAD_BY_ID_COMPLIANCE, value = "true")
|
||||
)
|
||||
@RequiresDialect(value = DerbyDialect.class)
|
||||
public class StoredProcedureTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp(EntityManagerFactoryScope scope) {
|
||||
createProcedures( scope.getEntityManagerFactory(), this::createProcedureSelectBydate );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown(EntityManagerFactoryScope scope) {
|
||||
createProcedures( scope.getEntityManagerFactory(), this::dropProcedures );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNotExistingStoredProcedureQuery(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Assertions.assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
entityManager.createStoredProcedureQuery(
|
||||
"NOT_EXISTING_NAME",
|
||||
"NOT_EXISTING_RESULT_MAPPING"
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetDateParameter(EntityManagerFactoryScope scope) throws Exception {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" );
|
||||
final Date d2 = formatter.parse( "2001-06-27" );
|
||||
final Date d3 = formatter.parse( "2002-07-07" );
|
||||
final Date d4 = formatter.parse( "2003-03-03" );
|
||||
final Date d5 = new Date();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
|
||||
entityManager.persist( new Person( 1, "Andrea", new Date() ) );
|
||||
entityManager.persist( new Person( 2, "Luigi", d2 ) );
|
||||
entityManager.persist( new Person( 3, "Massimiliano", d3 ) );
|
||||
entityManager.persist( new Person( 4, "Elisabetta", d4 ) );
|
||||
entityManager.persist( new Person( 5, "Roberto", d5 ) );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
StoredProcedureQuery storedProcedureQuery = entityManager.createStoredProcedureQuery( "SelectByDate" );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 1, Date.class, ParameterMode.IN );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT );
|
||||
storedProcedureQuery.setParameter( 1, new Date(), TemporalType.DATE );
|
||||
assertFalse( storedProcedureQuery.execute() );
|
||||
|
||||
Object outputParameterValue = storedProcedureQuery.getOutputParameterValue( 2 );
|
||||
assertInstanceOf( Integer.class, outputParameterValue );
|
||||
assertEquals( 1, outputParameterValue );
|
||||
|
||||
storedProcedureQuery = entityManager.createStoredProcedureQuery( "SelectByDate" );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 1, Date.class, ParameterMode.IN );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT );
|
||||
storedProcedureQuery.setParameter( 1, new Date(), TemporalType.DATE );
|
||||
|
||||
assertFalse( storedProcedureQuery.execute() );
|
||||
outputParameterValue = storedProcedureQuery.getOutputParameterValue( 2 );
|
||||
assertInstanceOf( Integer.class, outputParameterValue );
|
||||
assertEquals( 1, outputParameterValue );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetWrongParameterPosition(EntityManagerFactoryScope scope) {
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
StoredProcedureQuery storedProcedureQuery = entityManager
|
||||
.createStoredProcedureQuery( "SelectByDate" );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 1, Date.class, ParameterMode.IN );
|
||||
try {
|
||||
storedProcedureQuery.setParameter( 2, new Date(), TemporalType.DATE );
|
||||
fail( "IllegalArgumentException expected" );
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// ecxpected
|
||||
}
|
||||
|
||||
storedProcedureQuery = entityManager.createStoredProcedureQuery( "SelectByDate" );
|
||||
storedProcedureQuery.registerStoredProcedureParameter( 1, Date.class, ParameterMode.IN );
|
||||
Query q1 = storedProcedureQuery.setParameter( 1, new Date() );
|
||||
try {
|
||||
q1.setParameter( 2, new Date(), TemporalType.DATE );
|
||||
fail( "IllegalArgumentException expected" );
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
//expected
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private void createProcedures(EntityManagerFactory emf, Consumer<Statement> consumer) {
|
||||
final JdbcConnectionAccess connectionAccess = emf.unwrap( SessionFactoryImplementor.class ).getServiceRegistry()
|
||||
.getService( JdbcServices.class )
|
||||
.getBootstrapJdbcConnectionAccess();
|
||||
try (Connection conn = connectionAccess.obtainConnection()) {
|
||||
conn.setAutoCommit( false );
|
||||
|
||||
try (Statement statement = conn.createStatement()) {
|
||||
|
||||
consumer.accept( statement );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
conn.commit();
|
||||
}
|
||||
catch (SQLException e) {
|
||||
System.out.println( "Unable to commit transaction after creating creating procedures" );
|
||||
}
|
||||
|
||||
try {
|
||||
connectionAccess.releaseConnection( conn );
|
||||
}
|
||||
catch (SQLException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException( "Unable to create stored procedures", e );
|
||||
}
|
||||
}
|
||||
|
||||
private void dropProcedures(Statement statement) {
|
||||
try {
|
||||
statement.execute( "DROP PROCEDURE SelectByDate" );
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createProcedureSelectBydate(Statement statement) {
|
||||
try {
|
||||
statement.execute(
|
||||
"CREATE PROCEDURE SelectByDate(in IN_PARAM DATE, out OUT_PARAMAM INTEGER) " +
|
||||
"language java external name 'org.hibernate.orm.test.jpa.compliance.StoredProcedureTest.selectByDate' " +
|
||||
"parameter style java"
|
||||
);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
public static void selectByDate(java.sql.Date inParam, int[] outParam) throws SQLException {
|
||||
try (Connection con = DriverManager.getConnection( "jdbc:default:connection" )) {
|
||||
Statement stmt = con.createStatement();
|
||||
try (ResultSet rs = stmt.executeQuery( "SELECT ID FROM PERSON_TABLE where DATE_OF_BIRTH='" + inParam + "'" )) {
|
||||
if ( rs.next() ) {
|
||||
outParam[0] = rs.getInt( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "Person")
|
||||
@Table(name = "PERSON_TABLE")
|
||||
public static class Person {
|
||||
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
@Column(name = "DATE_OF_BIRTH")
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date dateOfBirth;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(Integer id, String name, Date dateOfBirth) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.dateOfBirth = dateOfBirth;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue