From 77b5568844b4a524e03e207be690030f384f533c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 3 Sep 2025 09:39:29 +0200 Subject: [PATCH 1/3] HHH-19750 Add test for issue --- .../CriteriaUpdateWithParametersTest.java | 146 ++++++++++++------ 1 file changed, 96 insertions(+), 50 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/CriteriaUpdateWithParametersTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/CriteriaUpdateWithParametersTest.java index 40f665c57625..b7a97d81280b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/CriteriaUpdateWithParametersTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/CriteriaUpdateWithParametersTest.java @@ -4,81 +4,113 @@ */ package org.hibernate.orm.test.jpa.query; -import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; -import org.hibernate.testing.orm.junit.Jpa; -import org.junit.jupiter.api.Test; - import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.Lob; import jakarta.persistence.Query; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.ParameterExpression; +import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Root; import jakarta.persistence.metamodel.EntityType; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; -@Jpa( - annotatedClasses = CriteriaUpdateWithParametersTest.Person.class -) + +@Jpa(annotatedClasses = { + CriteriaUpdateWithParametersTest.Person.class, + CriteriaUpdateWithParametersTest.Process.class +}) public class CriteriaUpdateWithParametersTest { @Test public void testCriteriaUpdate(EntityManagerFactoryScope scope) { - scope.inTransaction( - entityManager -> { - final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); - final Root root = criteriaUpdate.from( Person.class ); - - final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); - final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); - - final EntityType personEntityType = entityManager.getMetamodel().entity( Person.class ); - - criteriaUpdate.set( - root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), - intValueParameter - ); - criteriaUpdate.where( criteriaBuilder.equal( - root.get( personEntityType.getSingularAttribute( "name", String.class ) ), - stringValueParameter - ) ); - - final Query query = entityManager.createQuery( criteriaUpdate ); - query.setParameter( intValueParameter, 9 ); - query.setParameter( stringValueParameter, "Luigi" ); - - query.executeUpdate(); - } - ); + scope.inTransaction( entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); + + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + + final EntityType personEntityType = entityManager.getMetamodel().entity( Person.class ); + + criteriaUpdate.set( root.get( personEntityType.getSingularAttribute( "age", Integer.class ) ), + intValueParameter ); + criteriaUpdate.where( + criteriaBuilder.equal( root.get( personEntityType.getSingularAttribute( "name", String.class ) ), + stringValueParameter ) ); + + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); + + query.executeUpdate(); + } ); } @Test public void testCriteriaUpdate2(EntityManagerFactoryScope scope) { - scope.inTransaction( - entityManager -> { - final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); - final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); - final Root root = criteriaUpdate.from( Person.class ); + scope.inTransaction( entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + final CriteriaUpdate criteriaUpdate = criteriaBuilder.createCriteriaUpdate( Person.class ); + final Root root = criteriaUpdate.from( Person.class ); - final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); - final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); + final ParameterExpression intValueParameter = criteriaBuilder.parameter( Integer.class ); + final ParameterExpression stringValueParameter = criteriaBuilder.parameter( String.class ); - criteriaUpdate.set( "age", intValueParameter ); - criteriaUpdate.where( criteriaBuilder.equal( root.get( "name" ), stringValueParameter ) ); + criteriaUpdate.set( "age", intValueParameter ); + criteriaUpdate.where( criteriaBuilder.equal( root.get( "name" ), stringValueParameter ) ); - final Query query = entityManager.createQuery( criteriaUpdate ); - query.setParameter( intValueParameter, 9 ); - query.setParameter( stringValueParameter, "Luigi" ); + final Query query = entityManager.createQuery( criteriaUpdate ); + query.setParameter( intValueParameter, 9 ); + query.setParameter( stringValueParameter, "Luigi" ); - query.executeUpdate(); - } - ); + query.executeUpdate(); + } ); + } + + @Test + public void testCriteriaUpdate3(EntityManagerFactoryScope scope) { + scope.inTransaction( em -> { + // test separate value-bind parameters + final CriteriaBuilder cb = em.getCriteriaBuilder(); + final CriteriaUpdate cu = cb.createCriteriaUpdate( Process.class ); + final Root root = cu.from( Process.class ); + cu.set( root.get( "name" ), (Object) null ); + cu.set( root.get( "payload" ), (Object) null ); + em.createQuery( cu ).executeUpdate(); + } ); + + scope.inTransaction( em -> { + // test with the same cb.value( null ) parameter instance + final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) em.getCriteriaBuilder(); + final CriteriaUpdate cu = cb.createCriteriaUpdate( Process.class ); + final Root root = cu.from( Process.class ); + final Expression nullValue = cb.value( null ); + // a bit unfortunate, but we need to cast here to prevent ambiguous method references + final Path name = root.get( "name" ); + final Path payload = root.get( "payload" ); + final Expression nullString = cast( nullValue ); + final Expression nullBytes = cast( nullValue ); + cu.set( name, nullString ); + cu.set( payload, nullBytes ); + em.createQuery( cu ).executeUpdate(); + } ); + } + + private static Expression cast(Expression expression) { + //noinspection unchecked + return (Expression) expression; } @Entity(name = "Person") public static class Person { - @Id private String id; @@ -101,4 +133,18 @@ public Integer getAge() { return age; } } + + @Entity + public static class Process { + @Id + @GeneratedValue + private Long id; + + // All attributes below are necessary to reproduce the issue + + private String name; + + @Lob + private byte[] payload; + } } From dbeb793058246fc53227cc80cada997294ea7dd1 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 4 Sep 2025 11:18:25 +0200 Subject: [PATCH 2/3] HHH-19750 Consider value-bind criteria parameters as instance-unique --- .../ValueBindJpaCriteriaParameter.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java index 950551b4ee18..551041b1fbc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/ValueBindJpaCriteriaParameter.java @@ -10,7 +10,6 @@ import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmRenderContext; -import java.util.Objects; /** @@ -55,33 +54,17 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { } @Override - // TODO: fix this public int compareTo(SqmParameter parameter) { - return this == parameter ? 0 : 1; + return Integer.compare( hashCode(), parameter.hashCode() ); } - // this is not really a parameter, it's really a literal value - // so use value equality based on its value - @Override public boolean equals(Object object) { - return object instanceof ValueBindJpaCriteriaParameter that - && Objects.equals( this.value, that.value ); -// && getJavaTypeDescriptor().areEqual( this.value, (T) that.value ); + return this == object; } @Override public int hashCode() { - return value == null ? 0 : value.hashCode(); // getJavaTypeDescriptor().extractHashCode( value ); + return super.hashCode(); } - -// @Override -// public boolean equals(Object object) { -// return this == object; -// } -// -// @Override -// public int hashCode() { -// return System.identityHashCode( this ); -// } } From 9800672ed86d02a3ad663341f403160fd7a9c5a5 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 4 Sep 2025 12:31:47 +0200 Subject: [PATCH 3/3] HHH-19753 Correct equals/hashCode for `SqmFunction` and subtypes --- .../SelfRenderingSqmAggregateFunction.java | 21 ++++++++++++++++ .../function/SelfRenderingSqmFunction.java | 13 ---------- ...nderingSqmOrderedSetAggregateFunction.java | 21 ++++++++++++++++ .../SelfRenderingSqmWindowFunction.java | 25 +++++++++++++++++++ .../sqm/tree/expression/SqmFunction.java | 11 ++++++-- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java index 724a93b69823..366bc0e1c5b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmAggregateFunction.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.hibernate.metamodel.model.domain.ReturnableType; import org.hibernate.query.sqm.NodeBuilder; @@ -123,4 +124,24 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { hql.append( ')' ); } } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + if ( !super.equals( o ) ) { + return false; + } + + SelfRenderingSqmAggregateFunction that = (SelfRenderingSqmAggregateFunction) o; + return Objects.equals( filter, that.filter ); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Objects.hashCode( filter ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java index 58e35b583a2a..ea201483a9c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmFunction.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.function.Supplier; import org.hibernate.metamodel.mapping.BasicValuedMapping; @@ -255,16 +254,4 @@ public MappingModelExpressible get() { return argumentTypeResolver.resolveFunctionArgumentType( function.getArguments(), argumentIndex, converter ); } } - - @Override - // TODO: override on all subtypes - public boolean equals(Object other) { - return other instanceof SelfRenderingSqmAggregateFunction that - && Objects.equals( this.toHqlString(), that.toHqlString() ); - } - - @Override - public int hashCode() { - return toHqlString().hashCode(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java index 8125588036a5..a32b1f3c944e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmOrderedSetAggregateFunction.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.hibernate.metamodel.model.domain.ReturnableType; import org.hibernate.query.sqm.NodeBuilder; @@ -173,4 +174,24 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { hql.append( ')' ); } } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + if ( !super.equals( o ) ) { + return false; + } + + SelfRenderingSqmOrderedSetAggregateFunction that = (SelfRenderingSqmOrderedSetAggregateFunction) o; + return Objects.equals( withinGroup, that.withinGroup ); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Objects.hashCode( withinGroup ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java index 29caa8a2ae88..04dc8bdc62af 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SelfRenderingSqmWindowFunction.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.hibernate.metamodel.model.domain.ReturnableType; import org.hibernate.query.sqm.NodeBuilder; @@ -157,4 +158,28 @@ public void appendHqlString(StringBuilder hql, SqmRenderContext context) { hql.append( ')' ); } } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + if ( !super.equals( o ) ) { + return false; + } + + SelfRenderingSqmWindowFunction that = (SelfRenderingSqmWindowFunction) o; + return Objects.equals( filter, that.filter ) + && Objects.equals( respectNulls, that.respectNulls ) + && Objects.equals( fromFirst, that.fromFirst ); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Objects.hashCode( filter ); + result = 31 * result + Objects.hashCode( respectNulls ); + result = 31 * result + Objects.hashCode( fromFirst ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java index affda085cafd..3f2a4fc1599f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java @@ -206,8 +206,15 @@ public SqmPath resolveIndexedAccess( @Override public boolean equals(Object other) { - return other instanceof SqmFunction that - && Objects.equals( this.functionName, that.functionName ) + if ( this == other ) { + return true; + } + if ( other == null || getClass() != other.getClass() ) { + return false; + } + + final SqmFunction that = (SqmFunction) other; + return Objects.equals( this.functionName, that.functionName ) && Objects.equals( this.arguments, that.arguments ); }