added QueryDSL sorting support (#1653)

closes #1363
This commit is contained in:
Tigran Babloyan
2023-01-27 01:12:10 +04:00
committed by GitHub
parent 2866366b5c
commit ef9d3ff0fc
5 changed files with 91 additions and 10 deletions

View File

@@ -39,11 +39,13 @@ import com.querydsl.core.types.PathType;
import com.querydsl.core.types.SubQueryExpression;
import com.querydsl.core.types.TemplateExpression;
import com.querydsl.core.types.Visitor;
import com.querydsl.core.types.Order;
/**
* Serializes the given Querydsl query to a Document query for Couchbase.
*
* @author Michael Reiche
* @author Tigran Babloyan
*/
public abstract class CouchbaseDocumentSerializer implements Visitor<Object, Void> {
@@ -55,8 +57,15 @@ public abstract class CouchbaseDocumentSerializer implements Visitor<Object, Voi
Sort sort = Sort.unsorted();
for (OrderSpecifier<?> orderBy : orderBys) {
Object key = orderBy.getTarget().accept(this, null);
// sort.and(Sort.by(orderBy));
// sort.append(key.toString(), orderBy.getOrder() == Order.ASC ? 1 : -1);
String keyAsString = key.toString();
Sort.NullHandling sortNullHandling = switch (orderBy.getNullHandling()) {
case NullsFirst -> Sort.NullHandling.NULLS_FIRST;
case NullsLast -> Sort.NullHandling.NULLS_LAST;
default -> Sort.NullHandling.NATIVE;
};
Sort.Direction sortDirection = orderBy.getOrder() == Order.ASC ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort.Order sortOrder = new Sort.Order(sortDirection, keyAsString, sortNullHandling);
sort = sort.and(Sort.by(sortOrder));
}
return sort;
}

View File

@@ -30,6 +30,7 @@ import org.springframework.util.Assert;
* BasicQuery for Querydsl
*
* @author Michael Reiche
* @author Tigran Babloyan
*/
public class BasicQuery extends Query {
@@ -40,12 +41,11 @@ public class BasicQuery extends Query {
* {@link CouchbaseDocument}.
*
* @param query must not be {@literal null}.
* @param projectionFields must not be {@literal null}.
* @param projectionFields can be {@literal null}.
* @throws IllegalArgumentException when {@code sortObject} or {@code fieldsObject} is {@literal null}.
*/
public BasicQuery(Query query, Map<String, String> projectionFields) {
super(query);
Assert.notNull(projectionFields, "Field document must not be null");
this.projectionFields = projectionFields;
}

View File

@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
@@ -250,7 +251,7 @@ public class SpringDataCouchbaseQuery<T> extends SpringDataCouchbaseQuerySupport
@Nullable Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) {
Map<String, String> fields = createProjection(projection);
BasicQuery basicQuery = new BasicQuery(createCriteria(filter), fields);
BasicQuery basicQuery = filter == null ? new BasicQuery(new Query(), fields) : new BasicQuery(createCriteria(filter), fields);
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();

View File

@@ -29,6 +29,7 @@ import com.querydsl.couchbase.document.CouchbaseDocumentSerializer;
/**
* @author Michael Reiche
* @author Tigran Babloyan
*/
abstract class SpringDataCouchbaseQuerySupport<Q extends SpringDataCouchbaseQuerySupport<Q>>
extends AbstractCouchbaseQueryDSL<Q> {
@@ -84,10 +85,9 @@ abstract class SpringDataCouchbaseQuerySupport<Q extends SpringDataCouchbaseQuer
// sb.append(", ").append(projection.toJson(JSON_WRITER_SETTINGS, codec));
// }
sb.append(")");
// TODO
// if (!sort.isEmpty()) {
// sb.append(".sort(").append(sort.toJson(JSON_WRITER_SETTINGS, codec)).append(")");
// }
if (!sort.isEmpty()) {
sb.append(".sort(").append(sort).append(")");
}
if (getQueryMixin().getMetadata().getModifiers().getOffset() != null) {
sb.append(".skip(").append(getQueryMixin().getMetadata().getModifiers().getOffset()).append(")");
}
@@ -128,6 +128,6 @@ abstract class SpringDataCouchbaseQuerySupport<Q extends SpringDataCouchbaseQuer
* CouchbaseDocumentSerializer#toSort(List)
*/
protected Sort createSort(List<OrderSpecifier<?>> orderSpecifiers) {
return null; // TODO serializer.toSort(orderSpecifiers);
return serializer.toSort(orderSpecifiers);
}
}

View File

@@ -19,12 +19,15 @@ package org.springframework.data.couchbase.repository.query;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.springframework.data.couchbase.util.Util.comprises;
import static org.springframework.data.couchbase.util.Util.exactly;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.StreamSupport;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -53,6 +56,7 @@ import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@@ -67,6 +71,7 @@ import com.querydsl.core.types.dsl.BooleanExpression;
* Repository tests
*
* @author Michael Reiche
* @author Tigran Babloyan
*/
@SpringJUnitConfig(CouchbaseRepositoryQuerydslIntegrationTests.Config.class)
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@@ -410,6 +415,72 @@ public class CouchbaseRepositoryQuerydslIntegrationTests extends JavaIntegration
assertEquals(" WHERE name in $1", bq(predicate));
}
}
@Test
void testSort(){
{
BooleanExpression predicate = airline.name.in(Arrays.stream(saved).map(Airline::getName).toList());
Iterable<Airline> result = airlineRepository.findAll(predicate, Sort.by("name").ascending());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getName))
.toArray(Airline[]::new),
"Order of airlines does not match");
}
{
BooleanExpression predicate = airline.name.in(Arrays.stream(saved).map(Airline::getName).toList());
Iterable<Airline> result = airlineRepository.findAll(predicate, Sort.by("name").descending());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getName).reversed())
.toArray(Airline[]::new),
"Order of airlines does not match");
}
{
BooleanExpression predicate = airline.name.in(Arrays.stream(saved).map(Airline::getName).toList());
Iterable<Airline> result = airlineRepository.findAll(predicate, airline.name.asc());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getName))
.toArray(Airline[]::new),
"Order of airlines does not match");
}
{
BooleanExpression predicate = airline.name.in(Arrays.stream(saved).map(Airline::getName).toList());
Iterable<Airline> result = airlineRepository.findAll(predicate, airline.name.desc());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getName).reversed())
.toArray(Airline[]::new),
"Order of airlines does not match");
}
{
Comparator<String> nullSafeStringComparator = Comparator
.nullsFirst(String::compareTo);
Iterable<Airline> result = airlineRepository.findAll(airline.hqCountry.asc().nullsFirst());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getHqCountry, nullSafeStringComparator))
.toArray(Airline[]::new),
"Order of airlines does not match");
}
{
Comparator<String> nullSafeStringComparator = Comparator
.nullsFirst(String::compareTo);
Iterable<Airline> result = airlineRepository.findAll(airline.hqCountry.desc().nullsLast());
assertArrayEquals(StreamSupport.stream(result.spliterator(), false).toArray(Airline[]::new),
Arrays.stream(saved)
.sorted(Comparator.comparing(Airline::getHqCountry, nullSafeStringComparator).reversed())
.toArray(Airline[]::new),
"Order of airlines does not match");
}
}
@Test
void testNotIn() {