diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java index 9c1cae142a..8edfacbbd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/QueryParameters.java @@ -26,6 +26,7 @@ import org.hibernate.internal.util.EntityPrinter; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.internal.QueryParameterBindingsImpl; import org.hibernate.transform.ResultTransformer; +import org.hibernate.type.ComponentType; import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -535,7 +536,6 @@ public final class QueryParameters { StringBuilder result = new StringBuilder(); List parameters = new ArrayList(); List parameterTypes = new ArrayList(); - int positionalIndex = 0; while ( tokens.hasMoreTokens() ) { final String token = tokens.nextToken(); @@ -564,12 +564,26 @@ public final class QueryParameters { } } else { + result.append( token ); if ( "?".equals( token ) && positionalIndex < getPositionalParameterValues().length ) { + final Type type = getPositionalParameterTypes()[positionalIndex]; + if ( type.isComponentType() ) { + // should process tokens till reaching the number of "?" corresponding to the + // numberOfParametersCoveredBy of the compositeType + int paramIndex = 1; + final int numberOfParametersCoveredBy = getNumberOfParametersCoveredBy( ((ComponentType) type).getSubtypes() ); + while ( paramIndex < numberOfParametersCoveredBy ) { + final String nextToken = tokens.nextToken(); + if ( "?".equals( nextToken ) ) { + paramIndex++; + } + result.append( nextToken ); + } + } parameters.add( getPositionalParameterValues()[positionalIndex] ); - parameterTypes.add( getPositionalParameterTypes()[positionalIndex] ); + parameterTypes.add( type ); positionalIndex++; } - result.append( token ); } } processedPositionalParameterValues = parameters.toArray(); @@ -578,6 +592,19 @@ public final class QueryParameters { } } + private int getNumberOfParametersCoveredBy(Type[] subtypes) { + int numberOfParameters = 0; + for ( Type type : subtypes ) { + if ( type.isComponentType() ) { + numberOfParameters = numberOfParameters + getNumberOfParametersCoveredBy( ((ComponentType) type).getSubtypes() ); + } + else { + numberOfParameters++; + } + } + return numberOfParameters; + } + public String getFilteredSQL() { return processedSQL; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/filter/CriteriaQueryWithAppliedFilterTest.java b/hibernate-core/src/test/java/org/hibernate/test/filter/CriteriaQueryWithAppliedFilterTest.java new file mode 100644 index 0000000000..631e1df9b7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/filter/CriteriaQueryWithAppliedFilterTest.java @@ -0,0 +1,259 @@ +/* + * 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.filter; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; + +import org.junit.After; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.hamcrest.core.Is.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-10991") +public class CriteriaQueryWithAppliedFilterTest extends BaseCoreFunctionalTestCase { + + private final static Identifier STUDENT_ID = new Identifier( 2, new Identifier2( 4, 5L ) ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {Student.class}; + } + + @Override + protected void prepareTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + final Student student = new Student(); + student.setId( STUDENT_ID ); + student.setName( "dre" ); + student.setStatus( "active" ); + student.setAge( 21 ); + student.setAddress( new Address( "London", "Lollard St" ) ); + session.save( student ); + + final Student student2 = new Student(); + student2.setId( new Identifier( 4, new Identifier2( 4, 6L ) ) ); + student2.setName( "Livia" ); + student2.setStatus( "active" ); + student2.setAge( 27 ); + student2.setAddress( new Address( "London", "Oxford St" ) ); + session.save( student2 ); + }); + } + + @After + public void tearDown() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from Student" ).executeUpdate(); + } ); + } + + @Test + public void testSubquery() { + doInHibernate( this::sessionFactory, session -> { + final Criteria query = session.createCriteria( Student.class ); + query.add( Restrictions.eq( "name", "dre" ) ); + + final DetachedCriteria inner = DetachedCriteria.forClass( Student.class ); + inner.setProjection( Projections.min( "age" ) ); + + query.add( Property.forName( "age" ).eq( inner ) ); + query.add( Restrictions.eq( "name", "dre" ) ); + final List list = query.list(); + + assertThat( list.size(), is( 1 ) ); + }); + doInHibernate( this::sessionFactory, session -> { + session.enableFilter( "statusFilter" ).setParameter( "status", "deleted" ); + + final Criteria query = session.createCriteria( Student.class ); + query.add( Restrictions.eq( "name", "dre" ) ); + + final DetachedCriteria inner = DetachedCriteria.forClass( Student.class ); + inner.setProjection( Projections.min( "age" ) ); + + query.add( Property.forName( "age" ).eq( inner ) ); + query.add( Restrictions.eq( "name", "dre" ) ); + final List list = query.list(); + + assertThat( list.size(), is( 0 ) ); + }); + } + + @Test + public void testSubqueryWithRestrictionsOnComponentTypes() { + doInHibernate( this::sessionFactory, session -> { + session.enableFilter( "statusFilter" ).setParameter( "status", "active" ); + + final Criteria query = session.createCriteria( Student.class ); + query.add( Restrictions.eq( "id", STUDENT_ID ) ); + + final DetachedCriteria subSelect = DetachedCriteria.forClass( Student.class ); + subSelect.setProjection( Projections.max( "age" ) ); + subSelect.add( Restrictions.eq( "id", STUDENT_ID ) ); + + query.add( Property.forName( "age" ).eq( subSelect ) ); + final List list = query.list(); + + assertThat( list.size(), is( 1 ) ); + }); + } + + @Test + public void testSubqueryWithRestrictionsOnComponentTypes2() { + doInHibernate( this::sessionFactory, session -> { + session.enableFilter( "statusFilter" ).setParameter( "status", "active" ); + + final Criteria query = session.createCriteria( Student.class ); + query.add( Restrictions.eq( "id", STUDENT_ID ) ); + + final DetachedCriteria subSelect = DetachedCriteria.forClass( Student.class ); + subSelect.setProjection( Projections.max( "age" ) ); + subSelect.add( Restrictions.eq( "address", new Address( "London", "Lollard St" ) ) ); + subSelect.add( Restrictions.eq( "id", STUDENT_ID ) ); + + query.add( Property.forName( "age" ).eq( subSelect ) ); + final List list = query.list(); + + assertThat( list.size(), is( 1 ) ); + }); + } + + @Test + public void testRestrictionsOnComponentTypes() { + doInHibernate( this::sessionFactory, session -> { + session.enableFilter( "statusFilter" ).setParameter( "status", "active" ); + + final Criteria query = session.createCriteria( Student.class ); + query.add( Restrictions.eq( "id", STUDENT_ID ) ); + query.add( Restrictions.eq( "address", new Address( "London", "Lollard St" ) ) ); + query.add( Restrictions.eq( "name", "dre" ) ); + + final List list = query.list(); + + assertThat( list.size(), is( 1 ) ); + }); + } + + @FilterDef( + name = "statusFilter", + parameters = { + @ParamDef( + name = "status", type = "string" + ) + } + ) + @Filter(name = "statusFilter", condition = "STATUS = :status ") + @Entity(name = "Student") + @Table(name = "STUDENT") + public static class Student { + + @EmbeddedId + private Identifier id; + + private String name; + private int age; + + @Column(name = "STATUS") + private String status; + + @Embedded + private Address address; + + public void setId(Identifier id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setStatus(String status) { + this.status = status; + } + + public void setAge(int age) { + this.age = age; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Embeddable + public static class Identifier implements Serializable { + + private Integer id1; + + @Embedded + private Identifier2 id2; + + public Identifier() { + } + + public Identifier(Integer id1, Identifier2 id2) { + this.id1 = id1; + this.id2 = id2; + } + } + + @Embeddable + public static class Identifier2 implements Serializable { + + private Integer id3; + + private Long id4; + + public Identifier2() { + } + + public Identifier2(Integer id1, Long id2) { + this.id3 = id1; + this.id4 = id2; + } + } + + @Embeddable + public static class Address implements Serializable { + + private String city; + + private String street; + + public Address() { + } + + public Address(String city, String street) { + this.city = city; + this.street = street; + } + } +}