From 05a3ebc30bb20a847bfbc2385659ceda15dd8f15 Mon Sep 17 00:00:00 2001 From: Masafumi Miura Date: Wed, 27 Nov 2024 12:31:09 +0900 Subject: [PATCH 1/2] HHH-17151 Add test for issue --- .../test/jpa/query/DateTimeParameterTest.java | 136 ++++++++++-------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java index 489a0b6d1744..7a1bc5c3cbe8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/DateTimeParameterTest.java @@ -4,100 +4,118 @@ */ package org.hibernate.orm.test.jpa.query; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; import jakarta.persistence.Entity; -import jakarta.persistence.EntityManager; import jakarta.persistence.Id; +import jakarta.persistence.Parameter; import jakarta.persistence.Query; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; - -import org.junit.Test; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; /** * @author Steve Ebersole */ -public class DateTimeParameterTest extends BaseEntityManagerFunctionalTestCase { +@Jpa(annotatedClasses = {DateTimeParameterTest.Thing.class}) +public class DateTimeParameterTest { private static GregorianCalendar nowCal = new GregorianCalendar(); private static Date now = new Date( nowCal.getTime().getTime() ); - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Thing.class }; - } - @Test - public void testBindingCalendarAsDate() { - createTestData(); - - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - - try { - Query query = em.createQuery( "from Thing t where t.someDate = :aDate" ); + public void testBindingCalendarAsDate(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createQuery( "from Thing t where t.someDate = :aDate" ); query.setParameter( "aDate", nowCal, TemporalType.DATE ); - List list = query.getResultList(); - assertEquals( 1, list.size() ); - } - finally { - em.getTransaction().rollback(); - em.close(); - } - - deleteTestData(); + final List list = query.getResultList(); + assertThat( list.size() ).isEqualTo( 1 ); + } ); } @Test - public void testBindingNulls() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - - try { - Query query = em.createQuery( "from Thing t where t.someDate = :aDate or t.someTime = :aTime or t.someTimestamp = :aTimestamp" ); + public void testBindingNulls(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createQuery( + "from Thing t where t.someDate = :aDate or t.someTime = :aTime or t.someTimestamp = :aTimestamp" + ); query.setParameter( "aDate", (Date) null, TemporalType.DATE ); query.setParameter( "aTime", (Date) null, TemporalType.DATE ); query.setParameter( "aTimestamp", (Date) null, TemporalType.DATE ); - } - finally { - em.getTransaction().rollback(); - em.close(); - } + } ); + + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17151") + public void testBindingNullNativeQueryPositional(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createNativeQuery( "update Thing set someDate = ?1 where id = 1" ); + //noinspection deprecation + query.setParameter( 1, (Date) null, TemporalType.DATE ); + assertThat( query.executeUpdate() ).isEqualTo( 1 ); + } ); + scope.inTransaction( entityManager -> assertThat( entityManager.find( Thing.class, 1 ).someDate ).isNull() ); + } + + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-17151") + public void testBindingNullNativeQueryNamed(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final Query query = entityManager.createNativeQuery( "update Thing set someDate = :me where id = 1" ); + Parameter p = new Parameter<>() { + @Override + public String getName() { + return "me"; + } + + @Override + public Integer getPosition() { + return null; + } + + @Override + public Class getParameterType() { + return Date.class; + } + }; + //noinspection deprecation + query.setParameter( p, null, TemporalType.DATE ); + assertThat( query.executeUpdate() ).isEqualTo( 1 ); + } ); + scope.inTransaction( entityManager -> assertThat( entityManager.find( Thing.class, 1 ).someDate ).isNull() ); } - private void createTestData() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.persist( new Thing( 1, "test", now, now, now ) ); - em.getTransaction().commit(); - em.close(); + @BeforeEach + public void createTestData(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> entityManager.persist( new Thing( 1, "test", now, now, now ) ) ); } - private void deleteTestData() { - EntityManager em = getOrCreateEntityManager(); - em.getTransaction().begin(); - em.createQuery( "delete Thing" ).executeUpdate(); - em.getTransaction().commit(); - em.close(); + @AfterEach + public void deleteTestData(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); } - @Entity( name="Thing" ) - @Table( name = "THING" ) + @Entity(name = "Thing") + @Table(name = "Thing") public static class Thing { @Id public Integer id; public String someString; - @Temporal( TemporalType.DATE ) + @Temporal(TemporalType.DATE) public Date someDate; - @Temporal( TemporalType.TIME ) + @Temporal(TemporalType.TIME) public Date someTime; - @Temporal( TemporalType.TIMESTAMP ) + @Temporal(TemporalType.TIMESTAMP) public Date someTimestamp; public Thing() { From f04135e1aa81fe313d68828567b060d19b20b6d6 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 17 Dec 2024 09:57:08 +0100 Subject: [PATCH 2/2] HHH-17151 Resolve explicit temporal bind type for null values --- .../query/internal/BindingTypeHelper.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java b/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java index c94b9a28e534..4645d81a9f97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/BindingTypeHelper.java @@ -39,21 +39,27 @@ public BindableType resolveTemporalPrecision( BindableType declaredParameterType, BindingContext bindingContext) { if ( precision != null ) { - final SqmExpressible sqmExpressible = declaredParameterType.resolveExpressible(bindingContext); - if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) { - throw new UnsupportedOperationException( - "Cannot treat non-temporal parameter type with temporal precision" - ); + final TemporalJavaType temporalJtd; + if ( declaredParameterType != null ) { + final SqmExpressible sqmExpressible = declaredParameterType.resolveExpressible( bindingContext ); + if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) { + throw new UnsupportedOperationException( + "Cannot treat non-temporal parameter type with temporal precision" + ); + } + temporalJtd = (TemporalJavaType) sqmExpressible.getExpressibleJavaType(); + } + else { + temporalJtd = null; } - final TemporalJavaType temporalJtd = (TemporalJavaType) sqmExpressible.getExpressibleJavaType(); - if ( temporalJtd.getPrecision() != precision ) { + if ( temporalJtd == null || temporalJtd.getPrecision() != precision ) { final TypeConfiguration typeConfiguration = bindingContext.getTypeConfiguration(); final TemporalJavaType temporalTypeForPrecision; // Special case java.util.Date, because TemporalJavaType#resolveTypeForPrecision doesn't support widening, // since the main purpose of that method is to determine the final java type based on the reflective type // + the explicit @Temporal(TemporalType...) configuration - if ( java.util.Date.class.isAssignableFrom( temporalJtd.getJavaTypeClass() ) ) { + if ( temporalJtd == null || java.util.Date.class.isAssignableFrom( temporalJtd.getJavaTypeClass() ) ) { //noinspection unchecked temporalTypeForPrecision = (TemporalJavaType) typeConfiguration.getJavaTypeRegistry().getDescriptor( TemporalJavaType.resolveJavaTypeClass( precision )