Fix parameter binding validation

This commit is contained in:
Andrea Boriero 2022-02-07 11:43:47 +01:00 committed by Andrea Boriero
parent 4d024fde8b
commit 6fcbe5f0a0
5 changed files with 323 additions and 54 deletions

View File

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

View File

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

View File

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

View File

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

View File

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