HHH-10027 - Dynamic filter parameter can be bound in wrong order when applied to OneToMany collections

This commit is contained in:
Andrea Boriero 2016-07-30 15:58:54 +02:00 committed by Vlad Mihalcea
parent d62e9f44ca
commit c5e5287790
2 changed files with 186 additions and 6 deletions

View File

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
@ -23,6 +24,7 @@ import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.param.DynamicFilterParameterSpecification;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.DiscriminatorMetadata;
@ -677,24 +679,35 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
// ParameterContainer impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private List<ParameterSpecification> embeddedParameters;
private List<ParameterSpecification> embeddedParameters = new ArrayList<>( );
@Override
public void addEmbeddedParameter(ParameterSpecification specification) {
if ( embeddedParameters == null ) {
embeddedParameters = new ArrayList<ParameterSpecification>();
}
embeddedParameters.add( specification );
}
@Override
public boolean hasEmbeddedParameters() {
return embeddedParameters != null && ! embeddedParameters.isEmpty();
return !embeddedParameters.isEmpty();
}
@Override
public ParameterSpecification[] getEmbeddedParameters() {
return embeddedParameters.toArray( new ParameterSpecification[ embeddedParameters.size() ] );
final List<ParameterSpecification> parameterSpecification = getParameterSpecification();
return parameterSpecification.toArray( new ParameterSpecification[ parameterSpecification.size() ] );
}
private List<ParameterSpecification> getParameterSpecification() {
List<ParameterSpecification> parameterSpecifications =
embeddedParameters.stream()
.filter( o -> o instanceof DynamicFilterParameterSpecification )
.collect( Collectors.toList() );
parameterSpecifications.addAll(
embeddedParameters.stream()
.filter( o -> ! (o instanceof DynamicFilterParameterSpecification ) )
.collect( Collectors.toList() ) );
return parameterSpecifications;
}
public ParameterSpecification getIndexCollectionSelectorParamSpec() {

View File

@ -0,0 +1,167 @@
/*
* 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.test.filter;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.FilterDefs;
import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ParamDef;
import org.hibernate.query.Query;
import org.junit.Before;
import org.junit.Test;
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
*/
public class OneToManyWithDynamicFilterTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {ArticleRevision.class, ArticleTrading.class};
}
@Before
public void setUp() {
doInHibernate( this::sessionFactory, session -> {
ArticleTrading articleTrading = new ArticleTrading();
articleTrading.setClassifier( "no_classification" );
articleTrading.setPartyId( 2 );
articleTrading.setDeletionTimestamp( Timestamp.valueOf( "9999-12-31 00:00:00" ) );
articleTrading.setDeleted( true );
ArticleRevision revision = new ArticleRevision();
revision.addArticleTradings( articleTrading );
revision.setDeletionTimestamp( Timestamp.valueOf( "9999-12-31 00:00:00" ) );
revision.setDeleted( true );
session.save( revision );
} );
}
@Test
public void testForIssue() {
doInHibernate( this::sessionFactory, session -> {
final org.hibernate.Filter enableFilter = session.enableFilter( "aliveOnly" );
enableFilter.setParameter( "aliveTimestamp", Timestamp.valueOf( "9999-12-31 00:00:00" ) );
enableFilter.setParameter( "deleted", true );
enableFilter.validate();
final Query query = session.createQuery( "select a.id from ArticleRevision as a " +
"left join a.articleTradings as t " +
"with ( (t.partyId = :p_0) and (t.classifier = :p_1) )" );
query.setParameter( "p_0", 1L );
query.setParameter( "p_1", "no_classification" );
final List list = query.list();
assertThat( list.size(), is( 1 ) );
} );
}
@Entity(name = "ArticleRevision")
@Table(name = "REVISION")
@FilterDefs({
@FilterDef(name = "aliveOnly", parameters = {
@ParamDef(name = "aliveTimestamp", type = "timestamp"),
@ParamDef(name = "deleted", type = "boolean")
}, defaultCondition = "DELETION_TIMESTAMP = :aliveTimestamp and DELETED = :deleted")
})
@Filters({@Filter(name = "aliveOnly", condition = "DELETION_TIMESTAMP = :aliveTimestamp and DELETED = :deleted")})
public static class ArticleRevision {
@Id
@GeneratedValue
private long id;
@Column(name = "DELETION_TIMESTAMP")
private Timestamp deletionTimestamp;
@Column(name = "DELETED")
private boolean deleted;
@OneToMany(mappedBy = "articleRevision", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Filter(name = "aliveOnly")
private Set<ArticleTrading> articleTradings = new HashSet<ArticleTrading>();
public void setDeletionTimestamp(Timestamp deletionTimestamp) {
this.deletionTimestamp = deletionTimestamp;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public void addArticleTradings(ArticleTrading articleTrading) {
this.articleTradings.add( articleTrading );
articleTrading.setArticleRevision( this );
}
}
@Entity(name = "ArticleTrading")
@Table(name = "TRADING")
public static class ArticleTrading {
@Id
@GeneratedValue
private long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "articleRevision", nullable = false)
private ArticleRevision articleRevision;
private long partyId;
private String classifier;
@Column(name = "DELETED")
private boolean deleted;
@Column(name = "DELETION_TIMESTAMP")
protected Timestamp deletionTimestamp;
public void setArticleRevision(ArticleRevision articleRevision) {
this.articleRevision = articleRevision;
}
public void setPartyId(long partyId) {
this.partyId = partyId;
}
public void setClassifier(String classifier) {
this.classifier = classifier;
}
public void setDeletionTimestamp(Timestamp deletionTimestamp) {
this.deletionTimestamp = deletionTimestamp;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}
}