From 67cdd0b28a6ea5732841fcc8f1b7e9631a36fea7 Mon Sep 17 00:00:00 2001 From: The-Huginn Date: Wed, 22 Nov 2023 11:46:26 +0100 Subject: [PATCH] [HHH-17416] Add new inheritor JavaObjectType for specifying unresolved query parameter --- .../query/sqm/internal/TypecheckUtil.java | 7 ++ .../type/QueryParameterJavaObjectType.java | 21 +++++ .../hibernate/type/spi/TypeConfiguration.java | 17 +++- .../orm/test/query/hql/HHH17416Test.java | 77 +++++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/QueryParameterJavaObjectType.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/HHH17416Test.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java index dab3166fb4..dd84d30823 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java @@ -27,6 +27,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; +import org.hibernate.type.QueryParameterJavaObjectType; import org.hibernate.type.descriptor.jdbc.JdbcType; import java.time.temporal.Temporal; @@ -107,6 +108,12 @@ public static boolean areTypesComparable( return true; } + // for query with parameters we are unable to resolve the correct JavaType, especially for tuple of parameters + + if ( lhsType instanceof QueryParameterJavaObjectType || rhsType instanceof QueryParameterJavaObjectType) { + return true; + } + // since we can't so anything meaningful here, just allow // any comparison with multivalued parameters diff --git a/hibernate-core/src/main/java/org/hibernate/type/QueryParameterJavaObjectType.java b/hibernate-core/src/main/java/org/hibernate/type/QueryParameterJavaObjectType.java new file mode 100644 index 0000000000..d3e6ea86a3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/QueryParameterJavaObjectType.java @@ -0,0 +1,21 @@ +/* + * 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.type; + +public class QueryParameterJavaObjectType extends JavaObjectType { + + public static final QueryParameterJavaObjectType INSTANCE = new QueryParameterJavaObjectType(); + + public QueryParameterJavaObjectType() { + super(); + } + + @Override + public String getName() { + return "QUERY_PARAMETER_JAVA_OBJECT"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index e5180d3446..d186134a05 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -57,12 +57,14 @@ import org.hibernate.query.sqm.IntervalType; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.BasicType; import org.hibernate.type.BasicTypeRegistry; +import org.hibernate.type.QueryParameterJavaObjectType; import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaType; @@ -591,10 +593,17 @@ private Class entityClassForEntityName(String entityName) { public SqmExpressible resolveTupleType(List> typedNodes) { final SqmExpressible[] components = new SqmExpressible[typedNodes.size()]; for ( int i = 0; i < typedNodes.size(); i++ ) { - final SqmExpressible sqmExpressible = typedNodes.get( i ).getNodeType(); - components[i] = sqmExpressible != null - ? sqmExpressible - : getBasicTypeForJavaType( Object.class ); + SqmTypedNode tupleElement = typedNodes.get(i); + final SqmExpressible sqmExpressible = tupleElement.getNodeType(); + // keep null value for Named Parameters + if (tupleElement instanceof SqmParameter && sqmExpressible == null) { + components[i] = QueryParameterJavaObjectType.INSTANCE; + } + else { + components[i] = sqmExpressible != null + ? sqmExpressible + : getBasicTypeForJavaType( Object.class ); + } } return arrayTuples.computeIfAbsent( new ArrayCacheKey( components ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/HHH17416Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/HHH17416Test.java new file mode 100644 index 0000000000..3262b5d045 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/HHH17416Test.java @@ -0,0 +1,77 @@ +package org.hibernate.orm.test.query.hql; + +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.hibernate.orm.test.query.sqm.domain.Person; +import org.hibernate.orm.test.query.sqm.domain.Person_; +import org.hibernate.query.SelectionQuery; +import org.hibernate.query.SemanticException; +import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@JiraKey(value = "HHH-17416") +public class HHH17416Test extends BaseSessionFactoryFunctionalTest { + + private static final Person person = new Person(); + static { + person.setPk(7); + person.setNickName("Tadpole"); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Person.class}; + } + + @BeforeEach + public void setup() { + inTransaction(session -> session.persist(person)); + } + + @AfterEach + public void teardown() { + inTransaction(session -> session.createMutationQuery("delete from Person").executeUpdate()); + } + + @Test + public void testWhereClauseWithTuple() { + sessionFactoryScope().inTransaction( + entityManager -> { + SelectionQuery selectionQuery = entityManager.createSelectionQuery("from Person p where (p.id, p.nickName) = (:val1, :val2)", Person.class); + selectionQuery = selectionQuery.setParameter("val1", person.getPk()).setParameter("val2", person.getNickName()); + Person retrievedPerson = selectionQuery.getSingleResult(); + Assertions.assertEquals(person.getPk(), retrievedPerson.getPk()); + Assertions.assertEquals(person.getNickName(), retrievedPerson.getNickName()); + } + ); + } + + @Test + public void testWhereClauseWithInvalidObjectType() { + sessionFactoryScope().inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + + CriteriaQuery criteria = builder.createQuery(Person.class); + Root root = criteria.from(Person.class); + criteria.select(root); + + try { + criteria.where(builder.equal(root.get(Person_.nickName), builder.literal(new Object()))); + TypedQuery ignored = entityManager.createQuery(criteria); + Assertions.fail("Should have failed with 'Cannot compare left expression of type' of type `org.hibernate.query.SemanticException'"); + } + catch (Exception e) { + Assertions.assertTrue(e instanceof SemanticException); + Assertions.assertTrue(e.getMessage().startsWith("Cannot compare left expression of type")); + } + } + ); + } +}