From 5d36ecc12b14f8f6ccf88e94605008e3af181ee3 Mon Sep 17 00:00:00 2001 From: Subhashni Balakrishnan Date: Tue, 3 Jan 2017 15:07:25 -0800 Subject: [PATCH] DATACOUCH-265 - Support pageable and sort for String based SpEL queries Original pull request: #128. --- .../N1qlCouchbaseRepositoryTests.java | 28 ++++++++++++++++++- .../couchbase/repository/PartyRepository.java | 5 ++++ src/main/asciidoc/repository.adoc | 2 ++ .../query/StringN1qlBasedQuery.java | 28 ++++++++++++++++++- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java index 26a44d26..2d04bdfa 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryTests.java @@ -72,7 +72,7 @@ public class N1qlCouchbaseRepositoryTests { repository = factory.getRepository(PartyPagingRepository.class); partyRepository = factory.getRepository(PartyRepository.class); itemRepository = factory.getRepository(ItemRepository.class); - partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 0, null)); + partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 1, null)); itemRepository.save(new Item(KEY_ITEM, "MatchingDescription")); } @@ -151,4 +151,30 @@ public class N1qlCouchbaseRepositoryTests { List partyList = partyRepository.findByDescriptionOrName("MatchingDescription", "partyName"); assertTrue(partyList.size() == 1); } + + @Test + public void shouldPageWithStringBasedQuery() { + Pageable pageable = new PageRequest(0, 8, Sort.Direction.DESC, "attendees"); + Page page1 = partyRepository.findPartiesWithAttendee(1, pageable); + assertEquals(16, page1.getTotalElements()); //12 generated parties + 4 specifically crafted party + assertEquals(8, page1.getNumberOfElements()); + + List parties = page1.getContent(); + Long previousAttendees = null; + for (Party party : parties) { + if (previousAttendees != null) { + assertTrue(party.getAttendees() <= previousAttendees); + } + previousAttendees = party.getAttendees(); + } + Page page2 = partyRepository.findPartiesWithAttendee(1, page1.nextPageable()); + assertEquals(8, page2.getNumberOfElements()); + parties = page2.getContent(); + for (Party party : parties) { + if (previousAttendees != null) { + assertTrue(party.getAttendees() <= previousAttendees); + } + previousAttendees = party.getAttendees(); + } + } } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/PartyRepository.java b/src/integration/java/org/springframework/data/couchbase/repository/PartyRepository.java index b79dfec2..e4954ad7 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/PartyRepository.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/PartyRepository.java @@ -7,6 +7,8 @@ import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.query.View; import org.springframework.data.couchbase.core.query.ViewIndexed; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.Param; /** @@ -42,6 +44,9 @@ public interface PartyRepository extends CouchbaseRepository { @Query("SELECT 1 = 1") boolean justABoolean(); + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `attendees` >= $1") + Page findPartiesWithAttendee(int count, Pageable pageable); + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" + " AND `desc` NOT LIKE '%' || $excluded || '%'") List findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long minimumAttendees); diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index 8eba1730..9064d4b4 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -263,6 +263,8 @@ It adds two methods: TIP: You can also use `Page` and `Slice` as method return types as well with a N1QL backed repository. +NOTE: If pageable and sort parameters are used with inline queries, there should not be any order by, limit or offset clause in the inline query itself otherwise the server would reject the query as malformed. + The second way of querying, supported also in older versions of Couchbase Server, is the View-backed one that we'll see in the next section. [[couchbase.repository.views]] diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java index 382c8d98..64ea995c 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java @@ -20,14 +20,23 @@ package org.springframework.data.couchbase.repository.query; import com.couchbase.client.java.document.json.JsonValue; import com.couchbase.client.java.query.N1qlQuery; import com.couchbase.client.java.query.Statement; +import com.couchbase.client.java.query.dsl.path.DefaultLimitPath; +import com.couchbase.client.java.query.dsl.path.DefaultOrderByPath; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.repository.query.support.N1qlUtils; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ReturnedType; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; /** * A {@link RepositoryQuery} for Couchbase, based on N1QL and a String statement. @@ -76,7 +85,24 @@ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery { public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); String parsedStatement = this.queryParser.doParse(parser, evaluationContext, false); - return N1qlQuery.simple(parsedStatement).statement(); + String orderByPart = ""; + String limitByPart = ""; + + Sort sort = accessor.getSort(); + if (sort != null) { + com.couchbase.client.java.query.dsl.Sort[] cbSorts = N1qlUtils.createSort(sort, getCouchbaseOperations().getConverter()); + orderByPart = " " + new DefaultOrderByPath(null).orderBy(cbSorts).toString(); + } + if (queryMethod.isPageQuery()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable); + limitByPart = " " + new DefaultLimitPath(null).limit(pageable.getPageSize()).offset(pageable.getOffset()).toString(); + } else if (queryMethod.isSliceQuery()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable); + limitByPart = " " + new DefaultLimitPath(null).limit(pageable.getPageSize() + 1).offset(pageable.getOffset()).toString(); + } + return N1qlQuery.simple(parsedStatement + orderByPart + limitByPart).statement(); } @Override