Backport fix for Pageable in String Query. (#1260)
Backport from #1155. Closes #1259.
This commit is contained in:
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
@@ -55,6 +58,11 @@ public class StringQuery extends Query {
|
||||
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collection, Class domainClass,
|
||||
Class resultClass, boolean isCount, String[] distinctFields) {
|
||||
final StringBuilder statement = new StringBuilder();
|
||||
boolean makeCount = isCount && inlineN1qlQuery != null
|
||||
&& !inlineN1qlQuery.toLowerCase(Locale.ROOT).contains("count(");
|
||||
if (makeCount) {
|
||||
statement.append("SELECT COUNT(*) AS " + TemplateUtils.SELECT_COUNT + " FROM (");
|
||||
}
|
||||
appendInlineN1qlStatement(statement); // apply the string statement
|
||||
// To use generated parameters for literals
|
||||
// we need to figure out if we must use positional or named parameters
|
||||
@@ -68,9 +76,13 @@ public class StringQuery extends Query {
|
||||
paramIndexPtr = new int[] { -1 };
|
||||
}
|
||||
appendWhere(statement, paramIndexPtr, template.getConverter()); // criteria on this Query - should be empty for
|
||||
// StringQuery
|
||||
appendSort(statement);
|
||||
appendSkipAndLimit(statement);
|
||||
if (!isCount) {
|
||||
appendSort(statement);
|
||||
appendSkipAndLimit(statement);
|
||||
}
|
||||
if (makeCount) {
|
||||
statement.append(") predicate_query");
|
||||
}
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
|
||||
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProper
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -64,6 +66,17 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
|
||||
this.bucketName = bucketName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createQuery() {
|
||||
Query q = this.createQuery((Optional.of(this.accessor).map(ParameterAccessor::getSort).orElse(Sort.unsorted())));
|
||||
Pageable pageable = accessor.getPageable();
|
||||
if (pageable.isPaged()) {
|
||||
q.skip(pageable.getOffset());
|
||||
q.limit(pageable.getPageSize());
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
|
||||
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
|
||||
@@ -22,7 +22,6 @@ import static org.springframework.data.couchbase.core.support.TemplateUtils.SELE
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -30,17 +29,12 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
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.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
@@ -494,16 +488,7 @@ public class StringBasedN1qlQueryParser {
|
||||
runtimeParameters);
|
||||
N1QLExpression parsedStatement = x(this.doParse(parser, evaluationContext, isCountQuery));
|
||||
|
||||
Sort sort = accessor.getSort();
|
||||
if (sort.isSorted()) {
|
||||
N1QLExpression[] cbSorts = N1qlUtils.createSort(sort);
|
||||
parsedStatement = parsedStatement.orderBy(cbSorts);
|
||||
}
|
||||
if (queryMethod.isPageQuery()) {
|
||||
Pageable pageable = accessor.getPageable();
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
parsedStatement = parsedStatement.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset()));
|
||||
} else if (queryMethod.isSliceQuery()) {
|
||||
if (queryMethod.isSliceQuery()) {
|
||||
Pageable pageable = accessor.getPageable();
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
parsedStatement = parsedStatement.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset()));
|
||||
@@ -517,6 +502,12 @@ public class StringBasedN1qlQueryParser {
|
||||
for (Object o : accessor) {
|
||||
params.add(o);
|
||||
}
|
||||
if (accessor.getPageable() != null) {
|
||||
params.add(accessor.getPageable());
|
||||
}
|
||||
if (accessor.getSort() != null) {
|
||||
params.add(accessor.getSort());
|
||||
}
|
||||
return params.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.data.couchbase.domain;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
/**
|
||||
@@ -42,6 +42,7 @@ public class Airport extends ComparableEntity {
|
||||
|
||||
@CreatedBy private String createdBy;
|
||||
|
||||
private String[] organizations = new String[] { "123", "456", "789" };
|
||||
|
||||
@PersistenceConstructor
|
||||
public Airport(String id, String iata, String icao) {
|
||||
@@ -78,6 +79,7 @@ public class Airport extends ComparableEntity {
|
||||
version = Long.valueOf(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
@Repository
|
||||
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE ARRAY_CONTAINS(organizations, $1) AND #{#n1ql.filter}")
|
||||
Page<Airport> findAllByOrganizationIdsContains(String id, Pageable pageable);
|
||||
|
||||
@Override
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
List<Airport> findAll();
|
||||
@@ -97,4 +100,18 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
Optional<Airport> findByIdAndIata(String id, String iata);
|
||||
|
||||
@Query("SELECT 1 FROM `#{#n1ql.bucket}` WHERE anything = 'count(*)'") // looks like count query, but is not
|
||||
Long countBad();
|
||||
|
||||
@Query("SELECT count(*) FROM `#{#n1ql.bucket}`")
|
||||
Long countGood();
|
||||
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata != $1")
|
||||
Page<Airport> getAllByIataNot(String iata, Pageable pageable);
|
||||
|
||||
@Query("SELECT 1 FROM `#{#n1ql.bucket}` WHERE #{#n1ql.filter} " + " #{#projectIds != null ? 'AND blah IN $1' : ''} "
|
||||
+ " #{#planIds != null ? 'AND blahblah IN $2' : ''} " + " #{#active != null ? 'AND false = $3' : ''} ")
|
||||
Long countOne();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,42 +16,17 @@
|
||||
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import com.couchbase.client.core.error.CouchbaseException;
|
||||
import com.couchbase.client.core.error.IndexExistsException;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.RemoveResult;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.*;
|
||||
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.NOT_BOUNDED;
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
@@ -65,12 +40,54 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.NOT_BOUNDED;
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.RemoveResult;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.AirportDefaultConsistencyRepository;
|
||||
import org.springframework.data.couchbase.domain.AirportRepository;
|
||||
import org.springframework.data.couchbase.domain.Iata;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.domain.PersonRepository;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserRepository;
|
||||
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.couchbase.client.core.error.CouchbaseException;
|
||||
import com.couchbase.client.core.error.IndexExistsException;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Repository tests
|
||||
@@ -88,8 +105,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
|
||||
@Autowired AirportRepository airportRepository;
|
||||
|
||||
@Autowired
|
||||
AirportDefaultConsistencyRepository airportDefaultConsistencyRepository;
|
||||
@Autowired AirportDefaultConsistencyRepository airportDefaultConsistencyRepository;
|
||||
|
||||
@Autowired UserRepository userRepository;
|
||||
|
||||
@@ -195,6 +211,19 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findWithPageRequest() {
|
||||
Airport vie = null;
|
||||
try {
|
||||
vie = new Airport("airports::vie", "vie", "low6");
|
||||
vie = airportRepository.save(vie);
|
||||
// The exception occurred while building the query. This query will not find anything on execution.
|
||||
Page<Airport> airports = airportRepository.findAllByOrganizationIdsContains("123", PageRequest.of(0, 1));
|
||||
} finally {
|
||||
// airportRepository.delete(vie);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNotBounded() {
|
||||
// save() followed by query with NOT_BOUNDED will result in not finding the document
|
||||
@@ -234,7 +263,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class);
|
||||
// the Config class has been modified, these need to be loaded again
|
||||
CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
|
||||
AirportDefaultConsistencyRepository airportRepositoryRP = (AirportDefaultConsistencyRepository) ac.getBean("airportDefaultConsistencyRepository");
|
||||
AirportDefaultConsistencyRepository airportRepositoryRP = (AirportDefaultConsistencyRepository) ac
|
||||
.getBean("airportDefaultConsistencyRepository");
|
||||
|
||||
// save() followed by query with NOT_BOUNDED will result in not finding the document
|
||||
Airport vie = new Airport("airports::vie", "vie", "low9");
|
||||
@@ -272,7 +302,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class);
|
||||
// the Config class has been modified, these need to be loaded again
|
||||
CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
|
||||
AirportDefaultConsistencyRepository airportRepositoryRP = (AirportDefaultConsistencyRepository) ac.getBean("airportDefaultConsistencyRepository");
|
||||
AirportDefaultConsistencyRepository airportRepositoryRP = (AirportDefaultConsistencyRepository) ac
|
||||
.getBean("airportDefaultConsistencyRepository");
|
||||
|
||||
List<Airport> sizeBeforeTest = airportRepositoryRP.findAll();
|
||||
assertEquals(0, sizeBeforeTest.size());
|
||||
@@ -357,6 +388,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
void count() {
|
||||
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
|
||||
|
||||
airportRepository.countOne();
|
||||
try {
|
||||
|
||||
airportRepository.saveAll(
|
||||
@@ -366,6 +398,11 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false);
|
||||
assertEquals(1, count);
|
||||
|
||||
Pageable sPageable = PageRequest.of(0, 2).withSort(Sort.by("iata"));
|
||||
Page<Airport> sPage = airportRepository.getAllByIataNot("JFK", sPageable);
|
||||
assertEquals(iatas.length - 1, sPage.getTotalElements());
|
||||
assertEquals(sPageable.getPageSize(), sPage.getContent().size());
|
||||
|
||||
Pageable pageable = PageRequest.of(0, 2);
|
||||
Page<Airport> aPage = airportRepository.findAllByIataNot("JFK", pageable);
|
||||
assertEquals(iatas.length - 1, aPage.getTotalElements());
|
||||
@@ -392,6 +429,16 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void badCount() {
|
||||
assertThrows(CouchbaseQueryExecutionException.class, () -> airportRepository.countBad());
|
||||
}
|
||||
|
||||
@Test
|
||||
void goodCount() {
|
||||
airportRepository.countGood();
|
||||
}
|
||||
|
||||
@Test
|
||||
void threadSafeParametersTest() throws Exception {
|
||||
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
|
||||
|
||||
Reference in New Issue
Block a user