Skip to content

Commit

Permalink
HHH-14981 Support null precedence with Envers Query API
Browse files Browse the repository at this point in the history
  • Loading branch information
Naros committed Dec 17, 2021
1 parent bc65526 commit 2542173
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand Down Expand Up @@ -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).
*/
Expand Down Expand Up @@ -198,8 +199,8 @@ public Parameters addParameters(final String alias) {
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) {
Expand Down Expand Up @@ -382,10 +383,9 @@ public String getRootAlias() {

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;
}

Expand Down Expand Up @@ -481,4 +481,32 @@ public void appendJoin(boolean firstFromElement, StringBuilder builder, Map<Stri

}

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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public AuditQuery addOrder(AuditOrder order) {
orderEntityName,
orderData.getPropertyName()
);
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
qb.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public AuditAssociationQueryImpl<Q> addOrder(AuditOrder order) {
orderEntityName,
orderData.getPropertyName()
);
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending() );
queryBuilder.addOrder( orderEntityAlias, propertyName, orderData.isAscending(), orderData.getNullPrecedence() );
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ ORDER BY e.revision ASC (unless another order or projection is specified)

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 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
*/
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.
Expand All @@ -25,11 +33,13 @@ class OrderData {
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) {
Expand All @@ -44,6 +54,9 @@ public boolean isAscending() {
return ascending;
}

public NullPrecedence getNullPrecedence() {
return nullPrecedence;
}
}

}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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)
Expand All @@ -18,15 +19,22 @@ 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;
this.propertyNameGetter = propertyNameGetter;
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 );
}
}
Original file line number Diff line number Diff line change
@@ -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 );
}
}

0 comments on commit 2542173

Please sign in to comment.