DATACOUCH-484 - Thread Safety of query parameters.
This commit is contained in:
@@ -50,16 +50,21 @@ import org.springframework.util.Assert;
|
||||
public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
|
||||
private final PartTree partTree;
|
||||
private JsonValue placeHolderValues;
|
||||
private ThreadLocal<JsonValue> placeHolderValues;
|
||||
|
||||
public PartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) {
|
||||
super(queryMethod, couchbaseOperations);
|
||||
this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType());
|
||||
this.placeHolderValues = new ThreadLocal<JsonValue>() {
|
||||
@Override public JsonValue initialValue() {
|
||||
return JsonArray.create();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonValue getPlaceholderValues(ParameterAccessor accessor) {
|
||||
return this.placeHolderValues;
|
||||
return this.placeHolderValues.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,7 +75,7 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
N1qlCountQueryCreator queryCountCreator = new N1qlCountQueryCreator(partTree, accessor, countFrom,
|
||||
getCouchbaseOperations().getConverter(), getQueryMethod());
|
||||
Statement statement = queryCountCreator.createQuery();
|
||||
this.placeHolderValues = queryCountCreator.getPlaceHolderValues();
|
||||
this.placeHolderValues.set(queryCountCreator.getPlaceHolderValues());
|
||||
return statement;
|
||||
}
|
||||
|
||||
@@ -83,7 +88,7 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
DeleteUsePath deleteUsePath = deleteFrom(bucket);
|
||||
N1qlMutateQueryCreator mutateQueryCreator = new N1qlMutateQueryCreator(partTree, accessor, deleteUsePath, getCouchbaseOperations().getConverter(), getQueryMethod());
|
||||
MutateLimitPath mutateFromWhereOrderBy = mutateQueryCreator.createQuery();
|
||||
this.placeHolderValues = mutateQueryCreator.getPlaceHolderValues();
|
||||
this.placeHolderValues.set(mutateQueryCreator.getPlaceHolderValues());
|
||||
|
||||
if (partTree.isLimiting()) {
|
||||
return mutateFromWhereOrderBy.limit(partTree.getMaxResults());
|
||||
@@ -99,9 +104,9 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
}
|
||||
WherePath selectFrom = select.from(bucket);
|
||||
N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom,
|
||||
getCouchbaseOperations().getConverter(), getQueryMethod());
|
||||
getCouchbaseOperations().getConverter(), getQueryMethod());
|
||||
LimitPath selectFromWhereOrderBy = queryCreator.createQuery();
|
||||
this.placeHolderValues = queryCreator.getPlaceHolderValues();
|
||||
this.placeHolderValues.set(queryCreator.getPlaceHolderValues());
|
||||
|
||||
if (queryMethod.isPageQuery()) {
|
||||
Pageable pageable = accessor.getPageable();
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.springframework.data.couchbase;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.env.CouchbaseEnvironment;
|
||||
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.config.CouchbaseConfigurer;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.WriteResultChecking;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories(basePackages = "org.springframework.data.couchbase.repo")
|
||||
public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public String couchbaseAdminUser() {
|
||||
return "Administrator";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String couchbaseAdminPassword() {
|
||||
return "password";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getBootstrapHosts() {
|
||||
return Collections.singletonList("127.0.0.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketName() {
|
||||
return "protected";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketPassword() {
|
||||
return "password";
|
||||
}
|
||||
|
||||
// TODO maybe create the bucket if doesn't exist
|
||||
|
||||
@Override
|
||||
protected CouchbaseEnvironment getEnvironment() {
|
||||
return DefaultCouchbaseEnvironment.builder().connectTimeout(10000).kvTimeout(10000).queryTimeout(10000)
|
||||
.viewTimeout(10000).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseTemplate couchbaseTemplate() throws Exception {
|
||||
CouchbaseTemplate template = super.couchbaseTemplate();
|
||||
template.setWriteResultChecking(WriteResultChecking.LOG);
|
||||
return template;
|
||||
}
|
||||
|
||||
// this is for dev so it is ok to auto-create indexes
|
||||
@Override
|
||||
public IndexManager indexManager() {
|
||||
return new IndexManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Consistency getDefaultConsistency() {
|
||||
return Consistency.READ_YOUR_OWN_WRITES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CouchbaseConfigurer couchbaseConfigurer() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.couchbase.repo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.couchbase.repository.Item;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
@N1qlPrimaryIndexed
|
||||
public interface ItemRepository extends CrudRepository<Item, String> {
|
||||
|
||||
List<Object> findAllByDescriptionNotNull();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.couchbase.repo;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbasePagingAndSortingRepository;
|
||||
import org.springframework.data.couchbase.repository.Party;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository<Party, String> {
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.*;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.Party;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@Repository
|
||||
@ViewIndexed(designDoc = "party", viewName = "all")
|
||||
@N1qlPrimaryIndexed
|
||||
@N1qlSecondaryIndexed(indexName = "party")
|
||||
public interface PartyRepository extends CouchbaseRepository<Party, String> {
|
||||
|
||||
List<Party> findByAttendeesGreaterThanEqual(int minAttendees);
|
||||
|
||||
List<Party> findByName(String name);
|
||||
|
||||
List<Party> findByEventDateIs(Date targetDate);
|
||||
|
||||
@View(designDocument = "party", viewName = "byDate")
|
||||
List<Party> findFirst3ByEventDateGreaterThanEqual(Date targetDate);
|
||||
|
||||
List<Object> findAllByDescriptionNotNull();
|
||||
|
||||
long countAllByDescriptionNotNull();
|
||||
|
||||
@Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
long findMaxAttendees();
|
||||
|
||||
@Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
String findSomeString();
|
||||
|
||||
@Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
long countCustomPlusFive();
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
|
||||
long countCustom();
|
||||
|
||||
@Query("SELECT 1 = 1")
|
||||
boolean justABoolean();
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `attendees` >= $1")
|
||||
Page<Party> findPartiesWithAttendee(int count, Pageable pageable);
|
||||
|
||||
@Query("#{#n1ql.selectEntity}")
|
||||
List<Party> findParties(Sort sort);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" +
|
||||
" AND `desc` NOT LIKE '%' || $excluded || '%'")
|
||||
List<Party> findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc,
|
||||
@Param("min") long minimumAttendees);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
|
||||
" AND `desc` NOT LIKE '%' || $1 || '%'")
|
||||
List<Party> findAllWithPositionalParams(String ex, String inc, long minimumAttendees);
|
||||
|
||||
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees < $3" +
|
||||
" AND `desc` NOT LIKE '%' || $1 || '%' #{#n1ql.returning}")
|
||||
List<Party> removeWithPositionalParams(String ex, String inc, long minimumAttendees);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" +
|
||||
" AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"")
|
||||
List<Party> findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex,
|
||||
@Param("included") String inc, @Param("min") long min);
|
||||
|
||||
List<Party> findByDescriptionOrName(String description, String name);
|
||||
|
||||
List<Party> removeByDescriptionOrName(String description, String name);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1")
|
||||
List<Party> getByEventDate(Date eventDate);
|
||||
|
||||
List<Party> findByDescriptionStartingWith(String description);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
public class Item {
|
||||
|
||||
@Id
|
||||
public String id;
|
||||
|
||||
@Field("desc")
|
||||
public String description;
|
||||
|
||||
public Item(String id, String description) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Item item = (Item) o;
|
||||
|
||||
if (!id.equals(item.id)) return false;
|
||||
return !(description != null ? !description.equals(item.description) : item.description != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id.hashCode();
|
||||
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
//import org.springframework.data.couchbase.ContainerResourceRunner;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
// must not be in same package as CouchbaseRepository, otherwise AutoWired will fail on couchbaseRepository
|
||||
import org.springframework.data.couchbase.repo.ItemRepository;
|
||||
import org.springframework.data.couchbase.repo.PartyRepository;
|
||||
import org.springframework.data.couchbase.repo.PartyPagingRepository;
|
||||
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.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This has been copied from 3.2.x for testing backport of fix in DATACOUCH-484
|
||||
* This and dependent class have been copied from src/integration to src/test
|
||||
* and the repository classes have been moved from org.springframework.data.couchbase.repository
|
||||
* to org.springframework.data.couchbase.repo so that
|
||||
* org.springframework.data.couchbase.repository.CouchbaseRepository is not instantiated by
|
||||
* @Autowired
|
||||
*
|
||||
* This tests PaginAndSortingRepository features in the Couchbase connector.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Subhashni Balakrishnan
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
// @RunWith(ContainerResourceRunner.class)
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
|
||||
@SpringJUnitConfig(IntegrationTestApplicationConfig.class)
|
||||
@TestExecutionListeners(PartyPopulatorListener.class)
|
||||
@EnableCouchbaseRepositories
|
||||
public class N1qlCouchbaseRepositoryIntegrationTests {
|
||||
|
||||
@Autowired private PartyPagingRepository repository;
|
||||
|
||||
@Autowired private PartyRepository partyRepository;
|
||||
|
||||
@Autowired private ItemRepository itemRepository;
|
||||
|
||||
private final String KEY_PARTY = "Party1";
|
||||
private final String KEY_ITEM = "Item1";
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 1, null));
|
||||
itemRepository.save(new Item(KEY_ITEM, "MatchingDescription"));
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
try {
|
||||
itemRepository.deleteById(KEY_ITEM);
|
||||
} catch (DataRetrievalFailureException e) {}
|
||||
try {
|
||||
partyRepository.deleteById(KEY_PARTY);
|
||||
} catch (DataRetrievalFailureException e) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBeThreadsafe() {
|
||||
// This doesn't guarantee it, but we should catch most thread issues without
|
||||
// taking too long here...
|
||||
int runs = 50;
|
||||
for (int i = 0; i < runs; i++) {
|
||||
doShouldBeThreadsafe();
|
||||
}
|
||||
}
|
||||
|
||||
public void doShouldBeThreadsafe() {
|
||||
int threads = 50;
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
partyRepository.save(new Party(KEY_PARTY + thread, "Party like it's 199" + thread, "", null, 1, null));
|
||||
}
|
||||
ExecutorService service = Executors.newFixedThreadPool(threads);
|
||||
List<Callable<Boolean>> callables = new ArrayList<>();
|
||||
for (int thread = 0; thread < threads; ++thread) {
|
||||
final int counter = thread;
|
||||
Callable<Boolean> booleanSupplier = () -> {
|
||||
String expectedName = "Party like it's 199" + counter;
|
||||
String foundName = partyRepository.findByName(expectedName).get(0).getName();
|
||||
return expectedName.equals(foundName); // should never get false
|
||||
};
|
||||
callables.add(booleanSupplier);
|
||||
}
|
||||
try {
|
||||
List<Future<Boolean>> futures = service.invokeAll(callables);
|
||||
service.shutdown();
|
||||
service.awaitTermination(5, TimeUnit.SECONDS);
|
||||
for (Future<Boolean> future : futures) {
|
||||
assertTrue(future.get());
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
fail("Threads failed to run " + e.getMessage());
|
||||
}
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
partyRepository.delete(new Party(KEY_PARTY + thread, "Party like it's 199" + thread, "", null, 1, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAllWithSort() {
|
||||
Iterable<Party> allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees"));
|
||||
long previousAttendance = Long.MAX_VALUE;
|
||||
for (Party party : allByAttendanceDesc) {
|
||||
assertTrue(party.getAttendees() <= previousAttendance);
|
||||
previousAttendance = party.getAttendees();
|
||||
}
|
||||
assertFalse("Expected to find several parties", previousAttendance == Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() {
|
||||
Iterable<Party> parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc"));
|
||||
String previousDesc = null;
|
||||
for (Party party : parties) {
|
||||
if (previousDesc != null) {
|
||||
assertTrue(party.getDescription().compareTo(previousDesc) <= 0);
|
||||
}
|
||||
previousDesc = party.getDescription();
|
||||
}
|
||||
assertNotNull("Expected to find several parties", previousDesc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSortWithoutCaseSensitivity() {
|
||||
Iterable<Party> parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase()));
|
||||
String previousDesc = null;
|
||||
for (Party party : parties) {
|
||||
if (previousDesc != null) {
|
||||
assertTrue(party.getDescription().compareToIgnoreCase(previousDesc) <= 0);
|
||||
}
|
||||
previousDesc = party.getDescription();
|
||||
}
|
||||
assertNotNull("Expected to find several parties", previousDesc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPageThroughEntities() {
|
||||
Pageable pageable = PageRequest.of(0, 8);
|
||||
|
||||
Page<Party> page1 = repository.findAll(pageable);
|
||||
assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12);
|
||||
assertEquals(8, page1.getNumberOfElements());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPageThroughSortedEntities() {
|
||||
Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees");
|
||||
|
||||
Page<Party> page1 = repository.findAll(pageable);
|
||||
assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12);
|
||||
assertEquals(8, page1.getNumberOfElements());
|
||||
|
||||
List<Party> parties = page1.getContent();
|
||||
Long previousAttendees = null;
|
||||
for (Party party : parties) {
|
||||
if (previousAttendees != null) {
|
||||
assertTrue(party.getAttendees() <= previousAttendees);
|
||||
}
|
||||
previousAttendees = party.getAttendees();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWhereCriteria() {
|
||||
List<Party> partyList = partyRepository.findByDescriptionOrName("MatchingDescription", "partyName");
|
||||
assertTrue(partyList.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPageWithStringBasedQuery() {
|
||||
Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees");
|
||||
Page<Party> page1 = partyRepository.findPartiesWithAttendee(1, pageable);
|
||||
assertTrue("Query for parties with attendees should be atleast 12", page1.getTotalElements() >= 12);
|
||||
assertEquals(8, page1.getNumberOfElements());
|
||||
|
||||
List<Party> parties = page1.getContent();
|
||||
Long previousAttendees = null;
|
||||
for (Party party : parties) {
|
||||
if (previousAttendees != null) {
|
||||
assertTrue(party.getAttendees() <= previousAttendees);
|
||||
}
|
||||
previousAttendees = party.getAttendees();
|
||||
}
|
||||
Page<Party> 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Fails on deserialization as a different entity item is also present
|
||||
@Test(expected = MappingInstantiationException.class)
|
||||
public void shouldFailWithMissingFilterStringBasedQuery() {
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "attendees");
|
||||
partyRepository.findParties(sort);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteQuery() {
|
||||
partyRepository.save(new Party("testDeleteQuery", "delete", "delete", null, 0, null));
|
||||
List<Party> partyList = partyRepository.removeByDescriptionOrName("delete", "delete");
|
||||
assertTrue(partyList.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpelDateConvertion() {
|
||||
final String key = "testSpelDateConvertion";
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.clear();
|
||||
cal.set(2018, Calendar.SEPTEMBER, 10);
|
||||
Date date = cal.getTime();
|
||||
partyRepository.save(new Party(key, "", "", date, 0, null));
|
||||
List<Party> partyList = partyRepository.getByEventDate(date);
|
||||
assertTrue(partyList.size() == 1);
|
||||
assertEquals("Key mismatch", partyList.get(0).getKey(), key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testN1qlQueryWithInvalidValue() {
|
||||
partyRepository
|
||||
.save(new Party("testN1qlQueryWithInvalidValue", "", "testN1qlQueryWithInvalidValue", null, 0, null));
|
||||
final String description = "testN1qlQueryWithInvalidValue* OR `description` LIKE \"\"";
|
||||
List<Party> partyList = partyRepository.findByDescriptionStartingWith(description);
|
||||
assertTrue(partyList.size() == 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.geo.Point;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
/**
|
||||
* An entity used to test conversion of parameters in query derivations.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class Party {
|
||||
|
||||
@Id
|
||||
private final String key;
|
||||
|
||||
private final String name;
|
||||
|
||||
@Field("desc")
|
||||
private final String description;
|
||||
|
||||
private final Date eventDate;
|
||||
|
||||
private final long attendees;
|
||||
|
||||
private final Point location;
|
||||
|
||||
public Party(String key, String name, String description, Date eventDate, long attendees, Point location) {
|
||||
this.key = key;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.eventDate = eventDate;
|
||||
this.attendees = attendees;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Date getEventDate() {
|
||||
return eventDate;
|
||||
}
|
||||
|
||||
public long getAttendees() {
|
||||
return attendees;
|
||||
}
|
||||
|
||||
public Point getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Party party = (Party) o;
|
||||
|
||||
return key.equals(party.key);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Party{" +
|
||||
"name='" + name + '\'' +
|
||||
", eventDate=" + eventDate +
|
||||
", location=" + location +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.PersistTo;
|
||||
import com.couchbase.client.java.ReplicateTo;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import com.couchbase.client.java.view.DefaultView;
|
||||
import com.couchbase.client.java.view.DesignDocument;
|
||||
import com.couchbase.client.java.view.SpatialView;
|
||||
import com.couchbase.client.java.view.View;
|
||||
|
||||
import org.springframework.data.couchbase.config.BeanNames;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
|
||||
/**
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class PartyPopulatorListener extends DependencyInjectionTestExecutionListener {
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(final TestContext testContext) throws Exception {
|
||||
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
|
||||
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
|
||||
populateTestData(client, clusterInfo);
|
||||
createAndWaitForDesignDocs(client);
|
||||
}
|
||||
|
||||
private void populateTestData(Bucket client, ClusterInfo clusterInfo) {
|
||||
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.clear();
|
||||
cal.set(Calendar.YEAR, 2015);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 10);
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
Party p = new Party("testparty-" + i, "party like it's 199" + i,
|
||||
"An awesome party, 90's themed, every 10 of the month",
|
||||
cal.getTime(), 100 + i * 10,
|
||||
new Point(i, -i));
|
||||
template.save(p, PersistTo.MASTER, ReplicateTo.NONE);
|
||||
cal.roll(Calendar.MONTH, true);
|
||||
}
|
||||
|
||||
cal.clear();
|
||||
cal.set(Calendar.YEAR, 1990);
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 01);
|
||||
template.save(new Party("aTestParty", "New Year's Eve 90", "Happy New Year", cal.getTime(), 1230000, new Point(100, 100)));
|
||||
template.save(new Party("lowercaseParty", "lowercase party", "lowercase party", cal.getTime(), 1000, new Point(100, 100)));
|
||||
template.save(new Party("uppercaseParty", "Uppercase party", "Uppercase party", cal.getTime(), 1000, new Point(100, 100)));
|
||||
}
|
||||
|
||||
private void createAndWaitForDesignDocs(Bucket client) {
|
||||
//standard views
|
||||
List<View> views = new ArrayList<View>();
|
||||
String mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
|
||||
"{ emit(doc.eventDate, null); } }";
|
||||
views.add(DefaultView.create("byDate", mapFunction, "_count"));
|
||||
|
||||
//create the view design document
|
||||
DesignDocument designDoc = DesignDocument.create("party", views);
|
||||
client.bucketManager().upsertDesignDocument(designDoc);
|
||||
|
||||
//geo views
|
||||
List<View> geoViews = new ArrayList<View>();
|
||||
mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
|
||||
"{ emit([doc.location.x, doc.location.y], null); } }";
|
||||
geoViews.add(SpatialView.create("byLocation", mapFunction));
|
||||
|
||||
mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " +
|
||||
"{ emit([doc.location.x, doc.location.y, doc.attendees], null); } }";
|
||||
geoViews.add(SpatialView.create("byLocationAndAttendees", mapFunction));
|
||||
|
||||
//create the geo views design document
|
||||
DesignDocument geoDesignDoc = DesignDocument.create("partyGeo", geoViews);
|
||||
client.bucketManager().upsertDesignDocument(geoDesignDoc);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user