From cd86045598b4a1a25a87e11b42b4c237479bd669 Mon Sep 17 00:00:00 2001 From: Yordan Gigov Date: Tue, 13 Feb 2018 17:39:33 +0200 Subject: [PATCH] HHH-12292: nulls are valid values for Objects inside array --- .../spi/QueryParameterBindingValidator.java | 3 + .../QueryParametersValidationArrayTest.java | 202 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java index 69564a5edb..914c7e0d33 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingValidator.java @@ -113,6 +113,9 @@ public class QueryParameterBindingValidator { } return false; } + else if ( value == null) { + return true; + } else if ( expectedType.isInstance( value ) ) { return true; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java new file mode 100644 index 0000000000..b8457c56b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/QueryParametersValidationArrayTest.java @@ -0,0 +1,202 @@ +/* + * 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.test.hql; + +import java.sql.Array; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.NativeQuery; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.AbstractTypeDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.BasicBinder; +import org.hibernate.type.descriptor.sql.BasicExtractor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-12292") +@RequiresDialect(H2Dialect.class) +public class QueryParametersValidationArrayTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Event.class}; + } + + @Test + public void setParameterWithWrongTypeShouldNotThrowIllegalArgumentException() { + doInJPA(this::entityManagerFactory, entityManager -> { + entityManager.createNativeQuery( + "select id " + + "from Event " + + "where readings = :readings" ) + .unwrap( NativeQuery.class ) + .setParameter( "readings", new String[]{null, "a"}, StringArrayType.INSTANCE ) + .getResultList(); + }); + } + + @Entity(name = "Event") + public static class Event { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(columnDefinition = "ARRAY(1)") + private String[] readings; + } + + public static class StringArrayType + extends AbstractSingleColumnStandardBasicType { + + public static final StringArrayType INSTANCE = new StringArrayType (); + + public StringArrayType() { + super( StringArraySqlTypeDescriptor.INSTANCE, StringArrayTypeDescriptor.INSTANCE); + } + + public String getName() { + return "string-array"; + } + + @Override + protected boolean registerUnderJavaType() { + return true; + } + } + + public static class StringArraySqlTypeDescriptor implements SqlTypeDescriptor { + + public static final StringArraySqlTypeDescriptor INSTANCE = new StringArraySqlTypeDescriptor(); + + @Override + public int getSqlType() { + return Types.ARRAY; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + StringArrayTypeDescriptor arrayTypeDescriptor = (StringArrayTypeDescriptor) javaTypeDescriptor; + st.setArray(index, st.getConnection().createArrayOf( + arrayTypeDescriptor.getSqlArrayType(), + arrayTypeDescriptor.unwrap((String[]) value, Object[].class, options) + )); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + throw new UnsupportedOperationException("Binding by name is not supported!"); + } + }; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(rs.getArray(name), options); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(statement.getArray(index), options); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(statement.getArray(name), options); + } + }; + } + + } + + public static class StringArrayTypeDescriptor + extends AbstractTypeDescriptor { + + public static final StringArrayTypeDescriptor INSTANCE = new StringArrayTypeDescriptor(); + + public StringArrayTypeDescriptor() { + super(String[].class); + } + + public boolean areEqual(String[] one, String[] another) { + if ( one == another ) { + return true; + } + return !( one == null || another == null ) && Arrays.equals( one, another ); + } + + public String toString(String[] value) { + return Arrays.deepToString( value); + } + + @Override + public String[] fromString(String string) { + return null; + } + + @SuppressWarnings({"unchecked"}) + @Override + public X unwrap(String[] value, Class type, WrapperOptions options) { + return (X) value; + } + + @Override + public String[] wrap(X value, WrapperOptions options) { + if (value instanceof Array ) { + Array array = (Array) value; + try { + return (String[]) array.getArray(); + } catch (SQLException e) { + throw new IllegalArgumentException(e); + } + } + return (String[]) value; + } + + public String getSqlArrayType() { + return "varchar"; + } + } +}