HHH-14981 Support null precedence with Envers Query API
This commit is contained in:
parent
bc65526c77
commit
25421733d6
|
@ -30,6 +30,7 @@ import org.hibernate.envers.internal.tools.Triple;
|
|||
import org.hibernate.envers.query.criteria.AuditFunction;
|
||||
import org.hibernate.envers.query.criteria.AuditId;
|
||||
import org.hibernate.envers.query.criteria.AuditProperty;
|
||||
import org.hibernate.envers.query.order.NullPrecedence;
|
||||
import org.hibernate.envers.tools.Pair;
|
||||
import org.hibernate.query.Query;
|
||||
import org.hibernate.query.internal.QueryLiteralHelper;
|
||||
|
@ -63,9 +64,9 @@ public class QueryBuilder {
|
|||
*/
|
||||
private final List<JoinParameter> froms;
|
||||
/**
|
||||
* A list of triples (alias, property name, order ascending?).
|
||||
* A list of order by clauses.
|
||||
*/
|
||||
private final List<Triple<String, String, Boolean>> orders;
|
||||
private final List<OrderByClause> orders;
|
||||
/**
|
||||
* A list of complete projection definitions: either a sole property name, or a function(property name).
|
||||
*/
|
||||
|
@ -198,8 +199,8 @@ public class QueryBuilder {
|
|||
return result;
|
||||
}
|
||||
|
||||
public void addOrder(String alias, String propertyName, boolean ascending) {
|
||||
orders.add( Triple.make( alias, propertyName, ascending ) );
|
||||
public void addOrder(String alias, String propertyName, boolean ascending, NullPrecedence nullPrecedence) {
|
||||
orders.add( new OrderByClause( alias, propertyName, ascending, nullPrecedence ) );
|
||||
}
|
||||
|
||||
public void addOrderFragment(String alias, String orderByCollectionRole) {
|
||||
|
@ -382,10 +383,9 @@ public class QueryBuilder {
|
|||
|
||||
private List<String> getOrderList() {
|
||||
final List<String> orderList = new ArrayList<>();
|
||||
for ( Triple<String, String, Boolean> order : orders ) {
|
||||
orderList.add( order.getFirst() + "." + order.getSecond() + " " + (order.getThird() ? "asc" : "desc") );
|
||||
for ( OrderByClause orderByClause : orders ) {
|
||||
orderList.add( orderByClause.renderToHql() );
|
||||
}
|
||||
|
||||
return orderList;
|
||||
}
|
||||
|
||||
|
@ -481,4 +481,32 @@ public class QueryBuilder {
|
|||
|
||||
}
|
||||
|
||||
private static class OrderByClause {
|
||||
private String alias;
|
||||
private String propertyName;
|
||||
private boolean ascending;
|
||||
private NullPrecedence nullPrecedence;
|
||||
|
||||
public OrderByClause(String alias, String propertyName, boolean ascending, NullPrecedence nullPrecedence) {
|
||||
this.alias = alias;
|
||||
this.propertyName = propertyName;
|
||||
this.ascending = ascending;
|
||||
this.nullPrecedence = nullPrecedence;
|
||||
}
|
||||
|
||||
public String renderToHql() {
|
||||
StringBuilder hql = new StringBuilder();
|
||||
hql.append( alias ).append( "." ).append( propertyName ).append( " " );
|
||||
hql.append( ascending ? "asc" : "desc" );
|
||||
if ( nullPrecedence != null ) {
|
||||
if ( NullPrecedence.FIRST.equals( nullPrecedence ) ) {
|
||||
hql.append( " nulls first" );
|
||||
}
|
||||
else {
|
||||
hql.append( " nulls last" );
|
||||
}
|
||||
}
|
||||
return hql.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
|
|||
orderEntityName,
|
||||
orderData.getPropertyName()
|
||||
);
|
||||
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
|
||||
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
orderEntityName,
|
||||
orderData.getPropertyName()
|
||||
);
|
||||
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
|
||||
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
|||
|
||||
if ( !hasProjection() && !hasOrder ) {
|
||||
String revisionPropertyPath = configuration.getRevisionNumberPath();
|
||||
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true );
|
||||
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true, null );
|
||||
}
|
||||
|
||||
if ( !selectEntitiesOnly ) {
|
||||
|
|
|
@ -14,6 +14,14 @@ import org.hibernate.envers.configuration.Configuration;
|
|||
*/
|
||||
public interface AuditOrder {
|
||||
|
||||
/**
|
||||
* Specifies the null order precedence for the order-by column specification.
|
||||
*
|
||||
* @param nullPrecedence the null precedence, may be {@code null}.
|
||||
* @return this {@link AuditOrder} for chaining purposes
|
||||
*/
|
||||
AuditOrder nulls(NullPrecedence nullPrecedence);
|
||||
|
||||
/**
|
||||
* @param configuration the configuration
|
||||
* @return the order data.
|
||||
|
@ -25,11 +33,13 @@ public interface AuditOrder {
|
|||
private final String alias;
|
||||
private final String propertyName;
|
||||
private final boolean ascending;
|
||||
private final NullPrecedence nullPrecedence;
|
||||
|
||||
public OrderData(String alias, String propertyName, boolean ascending) {
|
||||
public OrderData(String alias, String propertyName, boolean ascending, NullPrecedence nullPrecedence) {
|
||||
this.alias = alias;
|
||||
this.propertyName = propertyName;
|
||||
this.ascending = ascending;
|
||||
this.nullPrecedence = nullPrecedence;
|
||||
}
|
||||
|
||||
public String getAlias(String baseAlias) {
|
||||
|
@ -44,6 +54,9 @@ public interface AuditOrder {
|
|||
return ascending;
|
||||
}
|
||||
|
||||
public NullPrecedence getNullPrecedence() {
|
||||
return nullPrecedence;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.envers.query.order;
|
||||
|
||||
/**
|
||||
* Defines the possible null handling modes.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
public enum NullPrecedence {
|
||||
/**
|
||||
* Null values will be rendered before non-null values.
|
||||
*/
|
||||
FIRST,
|
||||
|
||||
/**
|
||||
* Null values will be rendered after non-null values.
|
||||
*/
|
||||
LAST
|
||||
}
|
|
@ -9,6 +9,7 @@ package org.hibernate.envers.query.order.internal;
|
|||
import org.hibernate.envers.configuration.Configuration;
|
||||
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
|
||||
import org.hibernate.envers.query.order.AuditOrder;
|
||||
import org.hibernate.envers.query.order.NullPrecedence;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
|
@ -18,6 +19,7 @@ public class PropertyAuditOrder implements AuditOrder {
|
|||
private final String alias;
|
||||
private final PropertyNameGetter propertyNameGetter;
|
||||
private final boolean asc;
|
||||
private NullPrecedence nullPrecedence;
|
||||
|
||||
public PropertyAuditOrder(String alias, PropertyNameGetter propertyNameGetter, boolean asc) {
|
||||
this.alias = alias;
|
||||
|
@ -25,8 +27,14 @@ public class PropertyAuditOrder implements AuditOrder {
|
|||
this.asc = asc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditOrder nulls(NullPrecedence nullPrecedence) {
|
||||
this.nullPrecedence = nullPrecedence;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderData getData(Configuration configuration) {
|
||||
return new OrderData( alias, propertyNameGetter.get( configuration ), asc );
|
||||
return new OrderData( alias, propertyNameGetter.get( configuration ), asc, nullPrecedence );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.orm.test.envers.integration.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.envers.query.AuditEntity;
|
||||
import org.hibernate.envers.query.order.NullPrecedence;
|
||||
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
|
||||
import org.hibernate.orm.test.envers.Priority;
|
||||
import org.hibernate.orm.test.envers.entities.StrIntTestEntity;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.transaction.TransactionUtil;
|
||||
|
||||
/**
|
||||
* Tests for the {@link NullPrecedence} query option on order-bys.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-14981" )
|
||||
public class NullPrecedenceTest extends BaseEnversJPAFunctionalTestCase {
|
||||
|
||||
Integer id1;
|
||||
Integer id2;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { StrIntTestEntity.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
@Priority(10)
|
||||
public void initData() {
|
||||
// Revision 1
|
||||
id1 = TransactionUtil.doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
StrIntTestEntity entity1 = new StrIntTestEntity( null, 1 );
|
||||
entityManager.persist( entity1 );
|
||||
return entity1.getId();
|
||||
} );
|
||||
// Revision 2
|
||||
id2 = TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
StrIntTestEntity entity2 = new StrIntTestEntity( "two", 2 );
|
||||
entityManager.persist( entity2 );
|
||||
return entity2.getId();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPrecedenceFirst() {
|
||||
List results = getAuditReader().createQuery().forRevisionsOfEntity( StrIntTestEntity.class, true, false )
|
||||
.addProjection( AuditEntity.property( "number" ) )
|
||||
.addOrder( AuditEntity.property( "str1" ).asc().nulls( NullPrecedence.FIRST ) )
|
||||
.getResultList();
|
||||
List<Integer> expected = new ArrayList<>();
|
||||
expected.addAll( Arrays.asList( 1, 2 ) );
|
||||
Assert.assertEquals( expected, results );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPrecedenceLast() {
|
||||
List results = getAuditReader().createQuery().forRevisionsOfEntity( StrIntTestEntity.class, true, false )
|
||||
.addProjection( AuditEntity.property( "number" ) )
|
||||
.addOrder( AuditEntity.property( "str1" ).asc().nulls( NullPrecedence.LAST ) )
|
||||
.getResultList();
|
||||
List<Integer> expected = new ArrayList<>();
|
||||
expected.addAll( Arrays.asList( 2, 1 ) );
|
||||
Assert.assertEquals( expected, results );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue