Skip to content

Commit

Permalink
Fixing an issue where pages did not contain the expected content when…
Browse files Browse the repository at this point in the history
… using multiple and not-unique attributes as cursor properties (#31)
  • Loading branch information
p3t authored Aug 21, 2024
1 parent 825ac16 commit eb339cd
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 114 deletions.
2 changes: 1 addition & 1 deletion cursorpaging-examples/webapp-with-maven/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<java.version>17</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.32</lombok.version>
<cursorpage.version>0.8.0</cursorpage.version>
<cursorpage.version>0.8.1</cursorpage.version>
<testcontainers.version>1.19.8</testcontainers.version>
</properties>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ public static Filter attributeIs( final SingularAttribute<?, ? extends Comparabl
*/
public void apply( final QueryBuilder qb ) {
if ( values.size() > 1 ) {
qb.isIn( attribute, values );
qb.addWhere( qb.isIn( attribute, values ) );
} else {
qb.isEqual( attribute, values.get( 0 ) );
qb.addWhere( qb.equalTo( attribute, values.get( 0 ) ) );
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package io.vigier.cursorpaging.jpa;

import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

/**
* A position, which can be used to address the start of a page.
Expand All @@ -18,6 +22,7 @@
@Accessors( fluent = true )
@EqualsAndHashCode
@ToString
@Slf4j
public class Position {

/**
Expand Down Expand Up @@ -60,18 +65,27 @@ public boolean isFirst() {
/**
* Will apply the position information to the given {@link QueryBuilder}.
*
* @param qb builder where position information will be applied.
* @param qb builder where position information will be applied.
* @param preconditions preconditions to apply.
*/
public void apply( final QueryBuilder qb ) {
public void apply( final QueryBuilder qb, final List<Predicate> preconditions ) {
log.debug( "Position.apply ({} = {}, order = {})", attribute.name(), value, order );
if ( !isFirst() ) {
switch ( order ) {
case ASC -> qb.greaterThan( attribute, value );
case DESC -> qb.lessThan( attribute, value );
case ASC -> qb.addWhere( add( qb.greaterThan( attribute, value ), preconditions ) );
case DESC -> qb.addWhere( add( qb.lessThan( attribute, value ), preconditions ) );
}
}
qb.orderBy( attribute, order );
}

private List<Predicate> add( final Predicate condition, final List<Predicate> preconditions ) {
final List<Predicate> all = new ArrayList<>( preconditions.size() + 1 );
all.addAll( preconditions );
all.add( condition );
return all;
}

/**
* Will create a new {@link Position} taking over the attribute-values from the given entity.
*
Expand All @@ -82,4 +96,8 @@ public Position positionOf( final Object entity ) {
return toBuilder().value( attribute.valueOf( entity ) )
.build();
}

public Predicate getEquals( final QueryBuilder cqb ) {
return cqb.equalTo( attribute, value );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,14 @@
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.util.Collection;
import java.util.List;

/**
* Queries required by the cursor paging.
*
*/
public interface QueryBuilder {

/**
* In case of descending order the positions are less than the last one on the previous page.
*
* @param attribute the attribute to use for the query
* @param value the value to use for the query
*/
void lessThan( Attribute attribute, Comparable<?> value );

/**
* In case of ascending order the positions are greater than on the previous page
*
* @param attribute the attribute to use for the query
* @param value the value to use for the query
*/
void greaterThan( Attribute attribute, Comparable<?> value );

/**
* Provide the order for the position query
*
Expand All @@ -41,25 +26,20 @@ public interface QueryBuilder {
* Used to filter out not matching entities
*
* @param attribute the attribute to filter on
* @param value the value to filter on
*/
void isIn( Attribute attribute, Collection<? extends Comparable<?>> value );

/**
* Used to filter out not matching entities
*
* @param attribute the attribute to filter on
* @param value the value to filter on
* @param value the value to filter on
* @return the predicate to add to the query
*/
void isEqual( Attribute attribute, Comparable<?> value );
Predicate isIn( Attribute attribute, Collection<? extends Comparable<?>> value );

/**
* Low level access to add custom filter rules
*
* @param predicate
* @param predicate the predicate to add
*/
void addWhere( final Predicate predicate );

void addWhere( final List<Predicate> predicate );

/**
* Low level access to the query for the root-entity
*
Expand Down Expand Up @@ -89,4 +69,31 @@ public interface QueryBuilder {
* @return the entity manager
*/
EntityManager entityManager();

/**
* Get an equal predicate for the given attribute and value
*
* @param attribute the attribute
* @param value the value to be compared
* @return the predicate which can be added to the query
*/
Predicate equalTo( Attribute attribute, Comparable<?> value );

/**
* Get a greater than predicate for the given attribute and value
*
* @param attribute the attribute
* @param value the value to be compared
* @return the predicate which can be added to the query
*/
Predicate greaterThan( Attribute attribute, Comparable<?> value );

/**
* Get a less than predicate for the given attribute and value
*
* @param attribute the attribute
* @param value the value to be compared
* @return the predicate which can be added to the query
*/
Predicate lessThan( final Attribute attribute, final Comparable<?> value );
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,21 @@
*/
@Getter
@Accessors( fluent = true )
@Builder
@Builder( toBuilder = true )
@RequiredArgsConstructor
public class CriteriaQueryBuilder<E, R> implements QueryBuilder {

public enum AppendMode {
AND, OR
}

private final CriteriaQuery<R> query;
private final CriteriaBuilder cb;
private final Root<E> root;
private final Class<E> entityType;
private final EntityManager entityManager;
@Builder.Default
private final AppendMode appendMode = AppendMode.AND;

public static <T> CriteriaQueryBuilder<T, T> forEntity( final Class<T> entityType,
final EntityManager entityManager ) {
Expand Down Expand Up @@ -65,22 +71,27 @@ public static <E> CriteriaQueryBuilder<E, Long> forCount( final Class<E> entityT
.build();
}

public CriteriaQueryBuilder<E, R> orCondition() {
return toBuilder().appendMode( AppendMode.OR )
.build();
}

@Override
public void lessThan( final Attribute attribute, final Comparable<?> value ) {
whereLessThan( attribute, attribute.type().cast( value ) );
public Predicate lessThan( final Attribute attribute, final Comparable<?> value ) {
return createLessThan( attribute, attribute.type().cast( value ) );
}

private <V extends Comparable<? super V>> void whereLessThan( final Attribute attribute, final V value ) {
addWhere( cb().lessThan( attribute.path( root ), value ) );
private <V extends Comparable<? super V>> Predicate createLessThan( final Attribute attribute, final V value ) {
return cb.lessThan( attribute.path( root ), value );
}

@Override
public void greaterThan( final Attribute attribute, final Comparable<?> value ) {
whereGreaterThan( attribute, attribute.type().cast( value ) );
public Predicate greaterThan( final Attribute attribute, final Comparable<?> value ) {
return createGreaterThan( attribute, attribute.type().cast( value ) );
}

private <V extends Comparable<? super V>> void whereGreaterThan( final Attribute attribute, final V value ) {
addWhere( cb().greaterThan( attribute.path( root ), value ) );
private <V extends Comparable<? super V>> Predicate createGreaterThan( final Attribute attribute, final V value ) {
return cb().greaterThan( attribute.path( root ), value );
}

@Override
Expand All @@ -94,22 +105,30 @@ public void orderBy( final Attribute attribute, final Order order ) {
}

@Override
public void isIn( final Attribute attribute, final Collection<? extends Comparable<?>> value ) {
addWhere( attribute.path( root ).in( value ) );
public Predicate isIn( final Attribute attribute, final Collection<? extends Comparable<?>> value ) {
return attribute.path( root ).in( value );
}

@Override
public void isEqual( final Attribute attribute, final Comparable<?> value ) {
addWhere( cb().equal( attribute.path( root ), value ) );
public Predicate equalTo( final Attribute attribute, final Comparable<?> value ) {
return cb.equal( attribute.path( root ), value );
}

@Override
public void addWhere( final Predicate predicate ) {
final var restriction = query().getRestriction();
public void addWhere( final List<Predicate> conditions ) {
final var restriction = query.getRestriction();
if ( restriction == null ) {
query().where( predicate );
query.where( conditions.toArray( new Predicate[0] ) );
} else {
query().where( restriction, predicate );
switch ( appendMode ) {
case AND -> query.where( cb.and( conditions.toArray( new Predicate[0] ) ) );
case OR -> query.where( cb.or( restriction, cb.and( conditions.toArray( new Predicate[0] ) ) ) );
}
}
}

@Override
public void addWhere( final Predicate predicate ) {
addWhere( List.of( predicate ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import io.vigier.cursorpaging.jpa.PageRequest;
import io.vigier.cursorpaging.jpa.repository.CursorPageRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.Predicate;
import java.util.LinkedList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
Expand Down Expand Up @@ -49,7 +51,11 @@ public Page<E> loadPage( final PageRequest<E> request ) {
final CriteriaQueryBuilder<E, E> cqb = CriteriaQueryBuilder.forEntity( entityInformation.getJavaType(),
entityManager );

request.positions().forEach( position -> position.apply( cqb ) );
final List<Predicate> positionEquals = new LinkedList<>();
request.positions().forEach( position -> {
position.apply( cqb.orCondition(), positionEquals );
positionEquals.add( position.getEquals( cqb ) );
} );
request.filters().forEach( filter -> filter.apply( cqb ) );
request.rules().forEach( rule -> rule.applyQuery( cqb ) );

Expand Down
Loading

0 comments on commit eb339cd

Please sign in to comment.