Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.2] HHH-17151 NPE when binding null parameter in native query with explicit TemporalType #9444

Merged
merged 2 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,40 @@ public <T> BindableType<T> resolveTemporalPrecision(
BindableType<T> declaredParameterType,
SessionFactoryImplementor sessionFactory) {
if ( precision != null ) {
final SqmExpressible<T> sqmExpressible = declaredParameterType.resolveExpressible( sessionFactory );
if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) {
throw new UnsupportedOperationException(
"Cannot treat non-temporal parameter type with temporal precision"
);
final TemporalJavaType<T> temporalJtd;
if ( declaredParameterType != null ) {
final SqmExpressible<T> sqmExpressible = declaredParameterType.resolveExpressible( sessionFactory );
if ( !( JavaTypeHelper.isTemporal( sqmExpressible.getExpressibleJavaType() ) ) ) {
throw new UnsupportedOperationException(
"Cannot treat non-temporal parameter type with temporal precision"
);
}
temporalJtd = (TemporalJavaType<T>) sqmExpressible.getExpressibleJavaType();
}
else {
temporalJtd = null;
}

final TemporalJavaType<T> temporalJtd = (TemporalJavaType<T>) sqmExpressible.getExpressibleJavaType();
if ( temporalJtd.getPrecision() != precision ) {
if ( temporalJtd == null || temporalJtd.getPrecision() != precision ) {
final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration();
final TemporalJavaType<T> 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 ( temporalJtd == null || java.util.Date.class.isAssignableFrom( temporalJtd.getJavaTypeClass() ) ) {
//noinspection unchecked
temporalTypeForPrecision = (TemporalJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor(
TemporalJavaType.resolveJavaTypeClass( precision )
);
}
else {
temporalTypeForPrecision = temporalJtd.resolveTypeForPrecision(
precision,
typeConfiguration
);
}
return typeConfiguration.getBasicTypeRegistry().resolve(
temporalJtd.resolveTypeForPrecision( precision, typeConfiguration ),
temporalTypeForPrecision,
TemporalJavaType.resolveJdbcTypeCode( precision )
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ static int resolveJdbcTypeCode(TemporalType requestedTemporalPrecision) {
throw new UnsupportedOperationException( "Unsupported precision: " + requestedTemporalPrecision );
}

static Class<?> resolveJavaTypeClass(TemporalType requestedTemporalPrecision) {
switch ( requestedTemporalPrecision ) {
case DATE:
return java.sql.Date.class;
case TIME:
return java.sql.Time.class;
case TIMESTAMP:
return java.sql.Timestamp.class;
}
throw new UnsupportedOperationException( "Unsupported precision: " + requestedTemporalPrecision );
}

/**
* The precision represented by this type
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,100 +6,120 @@
*/
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 java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.junit.Test;
import org.hibernate.SessionFactory;

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<Date> p = new Parameter<>() {
@Override
public String getName() {
return "me";
}

@Override
public Integer getPosition() {
return null;
}

@Override
public Class<Date> 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.inTransaction( entityManager -> entityManager.createQuery( "delete from Thing" ).executeUpdate() );
}

@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() {
Expand Down
Loading