From c3bda5899eeab44206b2699539654b3120245c71 Mon Sep 17 00:00:00 2001 From: Tigran Babloyan Date: Thu, 26 Jan 2023 12:48:19 +0400 Subject: [PATCH] added QueryDSL sorting support closes #1363 --- .../document/CouchbaseDocumentSerializer.java | 13 +++- .../repository/support/BasicQuery.java | 4 +- .../support/SpringDataCouchbaseQuery.java | 3 +- .../SpringDataCouchbaseQuerySupport.java | 10 +-- ...aseRepositoryQuerydslIntegrationTests.java | 71 +++++++++++++++++++ 5 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/querydsl/couchbase/document/CouchbaseDocumentSerializer.java b/src/main/java/com/querydsl/couchbase/document/CouchbaseDocumentSerializer.java index 8a7462035..310c67529 100644 --- a/src/main/java/com/querydsl/couchbase/document/CouchbaseDocumentSerializer.java +++ b/src/main/java/com/querydsl/couchbase/document/CouchbaseDocumentSerializer.java @@ -39,11 +39,13 @@ 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 { @@ -55,8 +57,15 @@ public Sort toSort(List> orderBys) { 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; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/BasicQuery.java b/src/main/java/org/springframework/data/couchbase/repository/support/BasicQuery.java index fadbe804f..d3294d38c 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/BasicQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/BasicQuery.java @@ -30,6 +30,7 @@ * 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 projectionFields) { super(query); - Assert.notNull(projectionFields, "Field document must not be null"); this.projectionFields = projectionFields; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuery.java index 8f6e73416..e5105a872 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuery.java @@ -26,6 +26,7 @@ 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 @@ protected org.springframework.data.couchbase.core.query.Query createQuery(@Nulla @Nullable Expression projection, QueryModifiers modifiers, List> orderBy) { Map 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(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuerySupport.java b/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuerySupport.java index 1dd0ff0ca..f02d55a36 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuerySupport.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SpringDataCouchbaseQuerySupport.java @@ -29,6 +29,7 @@ /** * @author Michael Reiche + * @author Tigran Babloyan */ abstract class SpringDataCouchbaseQuerySupport> extends AbstractCouchbaseQueryDSL { @@ -84,10 +85,9 @@ public String toString() { // 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 @@ public CouchbaseDocument asDocument() { * CouchbaseDocumentSerializer#toSort(List) */ protected Sort createSort(List> orderSpecifiers) { - return null; // TODO serializer.toSort(orderSpecifiers); + return serializer.toSort(orderSpecifiers); } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuerydslIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuerydslIntegrationTests.java index daf0dfb64..18c56438a 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuerydslIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuerydslIntegrationTests.java @@ -19,12 +19,15 @@ 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.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 @@ * Repository tests * * @author Michael Reiche + * @author Tigran Babloyan */ @SpringJUnitConfig(CouchbaseRepositoryQuerydslIntegrationTests.Config.class) @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) @@ -410,6 +415,72 @@ void testIn() { assertEquals(" WHERE name in $1", bq(predicate)); } } + + @Test + void testSort(){ + { + BooleanExpression predicate = airline.name.in(Arrays.stream(saved).map(Airline::getName).toList()); + Iterable 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 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 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 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 nullSafeStringComparator = Comparator + .nullsFirst(String::compareTo); + Iterable 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 nullSafeStringComparator = Comparator + .nullsFirst(String::compareTo); + Iterable 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() {