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.AuditFunction;
|
||||||
import org.hibernate.envers.query.criteria.AuditId;
|
import org.hibernate.envers.query.criteria.AuditId;
|
||||||
import org.hibernate.envers.query.criteria.AuditProperty;
|
import org.hibernate.envers.query.criteria.AuditProperty;
|
||||||
|
import org.hibernate.envers.query.order.NullPrecedence;
|
||||||
import org.hibernate.envers.tools.Pair;
|
import org.hibernate.envers.tools.Pair;
|
||||||
import org.hibernate.query.Query;
|
import org.hibernate.query.Query;
|
||||||
import org.hibernate.query.internal.QueryLiteralHelper;
|
import org.hibernate.query.internal.QueryLiteralHelper;
|
||||||
|
@ -63,9 +64,9 @@ public class QueryBuilder {
|
||||||
*/
|
*/
|
||||||
private final List<JoinParameter> froms;
|
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).
|
* 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOrder(String alias, String propertyName, boolean ascending) {
|
public void addOrder(String alias, String propertyName, boolean ascending, NullPrecedence nullPrecedence) {
|
||||||
orders.add( Triple.make( alias, propertyName, ascending ) );
|
orders.add( new OrderByClause( alias, propertyName, ascending, nullPrecedence ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOrderFragment(String alias, String orderByCollectionRole) {
|
public void addOrderFragment(String alias, String orderByCollectionRole) {
|
||||||
|
@ -382,10 +383,9 @@ public class QueryBuilder {
|
||||||
|
|
||||||
private List<String> getOrderList() {
|
private List<String> getOrderList() {
|
||||||
final List<String> orderList = new ArrayList<>();
|
final List<String> orderList = new ArrayList<>();
|
||||||
for ( Triple<String, String, Boolean> order : orders ) {
|
for ( OrderByClause orderByClause : orders ) {
|
||||||
orderList.add( order.getFirst() + "." + order.getSecond() + " " + (order.getThird() ? "asc" : "desc") );
|
orderList.add( orderByClause.renderToHql() );
|
||||||
}
|
}
|
||||||
|
|
||||||
return orderList;
|
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,
|
orderEntityName,
|
||||||
orderData.getPropertyName()
|
orderData.getPropertyName()
|
||||||
);
|
);
|
||||||
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
|
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
||||||
orderEntityName,
|
orderEntityName,
|
||||||
orderData.getPropertyName()
|
orderData.getPropertyName()
|
||||||
);
|
);
|
||||||
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
|
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
||||||
|
|
||||||
if ( !hasProjection() && !hasOrder ) {
|
if ( !hasProjection() && !hasOrder ) {
|
||||||
String revisionPropertyPath = configuration.getRevisionNumberPath();
|
String revisionPropertyPath = configuration.getRevisionNumberPath();
|
||||||
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true );
|
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true, null );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !selectEntitiesOnly ) {
|
if ( !selectEntitiesOnly ) {
|
||||||
|
|
|
@ -14,6 +14,14 @@ import org.hibernate.envers.configuration.Configuration;
|
||||||
*/
|
*/
|
||||||
public interface AuditOrder {
|
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
|
* @param configuration the configuration
|
||||||
* @return the order data.
|
* @return the order data.
|
||||||
|
@ -25,11 +33,13 @@ public interface AuditOrder {
|
||||||
private final String alias;
|
private final String alias;
|
||||||
private final String propertyName;
|
private final String propertyName;
|
||||||
private final boolean ascending;
|
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.alias = alias;
|
||||||
this.propertyName = propertyName;
|
this.propertyName = propertyName;
|
||||||
this.ascending = ascending;
|
this.ascending = ascending;
|
||||||
|
this.nullPrecedence = nullPrecedence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAlias(String baseAlias) {
|
public String getAlias(String baseAlias) {
|
||||||
|
@ -44,6 +54,9 @@ public interface AuditOrder {
|
||||||
return ascending;
|
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.configuration.Configuration;
|
||||||
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
|
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
|
||||||
import org.hibernate.envers.query.order.AuditOrder;
|
import org.hibernate.envers.query.order.AuditOrder;
|
||||||
|
import org.hibernate.envers.query.order.NullPrecedence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
@ -18,6 +19,7 @@ public class PropertyAuditOrder implements AuditOrder {
|
||||||
private final String alias;
|
private final String alias;
|
||||||
private final PropertyNameGetter propertyNameGetter;
|
private final PropertyNameGetter propertyNameGetter;
|
||||||
private final boolean asc;
|
private final boolean asc;
|
||||||
|
private NullPrecedence nullPrecedence;
|
||||||
|
|
||||||
public PropertyAuditOrder(String alias, PropertyNameGetter propertyNameGetter, boolean asc) {
|
public PropertyAuditOrder(String alias, PropertyNameGetter propertyNameGetter, boolean asc) {
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
|
@ -25,8 +27,14 @@ public class PropertyAuditOrder implements AuditOrder {
|
||||||
this.asc = asc;
|
this.asc = asc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuditOrder nulls(NullPrecedence nullPrecedence) {
|
||||||
|
this.nullPrecedence = nullPrecedence;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OrderData getData(Configuration configuration) {
|
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