DATACOUCH-146 - Reactive repositories support
- Adds experimental support for reactor types in reactive CRUD and Sorting repositories using RxJava1Template - Refactor configuration and query classes to be reusable by reactive repositories Original pull request: #130.
This commit is contained in:
@@ -11,6 +11,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
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;
|
||||
@@ -77,4 +78,10 @@ public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfigura
|
||||
protected Consistency getDefaultConsistency() {
|
||||
return Consistency.READ_YOUR_OWN_WRITES;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected CouchbaseConfigurer couchbaseConfigurer() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.WriteResultChecking;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
|
||||
@Configuration
|
||||
public class ReactiveIntegrationTestApplicationConfig extends AbstractReactiveCouchbaseConfiguration {
|
||||
|
||||
@Autowired
|
||||
private Environment springEnv;
|
||||
|
||||
@Bean
|
||||
public String couchbaseAdminUser() {
|
||||
return springEnv.getProperty("couchbase.adminUser", "Administrator");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String couchbaseAdminPassword() {
|
||||
return springEnv.getProperty("couchbase.adminUser", "password");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getBootstrapHosts() {
|
||||
return Collections.singletonList(springEnv.getProperty("couchbase.host", "127.0.0.1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketName() {
|
||||
return springEnv.getProperty("couchbase.bucket", "default");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBucketPassword() {
|
||||
return springEnv.getProperty("couchbase.password", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CouchbaseEnvironment getEnvironment() {
|
||||
return DefaultCouchbaseEnvironment.builder()
|
||||
.connectTimeout(10000)
|
||||
.kvTimeout(10000)
|
||||
.queryTimeout(10000)
|
||||
.viewTimeout(10000)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception {
|
||||
RxJavaCouchbaseTemplate template = super.reactiveCouchbaseTemplate();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.WriteResultChecking;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -23,7 +25,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* This test case demonstrates that the {@link AbstractCouchbaseDataConfiguration} can take its SDK beans
|
||||
* This test case demonstrates that the {@link AbstractCouchbaseConfiguration} can take its SDK beans
|
||||
* from a sibling {@link Configuration}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
@@ -31,7 +33,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@SuppressWarnings("SpringJavaAutowiringInspection")
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class AbstractCouchbaseDataConfigurationTest {
|
||||
public class AbstractCouchbaseConfigurationTest {
|
||||
|
||||
@Autowired
|
||||
ItemRepository repository;
|
||||
@@ -72,10 +74,10 @@ public class AbstractCouchbaseDataConfigurationTest {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseDataConfigurationTest.class, considerNestedRepositories = true)
|
||||
static class Config extends AbstractCouchbaseDataConfiguration {
|
||||
@EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseConfigurationTest.class, considerNestedRepositories = true)
|
||||
abstract static class Config extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@Autowired
|
||||
@Autowired
|
||||
Cluster c;
|
||||
|
||||
@Autowired
|
||||
@@ -87,6 +89,25 @@ public class AbstractCouchbaseDataConfigurationTest {
|
||||
@Autowired
|
||||
CouchbaseEnvironment e;
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CouchbaseConfigurer couchbaseConfigurer() {
|
||||
return new TestCouchbaseConfigurer(e, c, ci, b);
|
||||
@@ -93,6 +93,8 @@ public class CouchbaseTemplateTests {
|
||||
@Test
|
||||
public void saveSimpleEntityCorrectly() throws Exception {
|
||||
String id = "beers:awesome-stout";
|
||||
removeIfExist(id);
|
||||
|
||||
String name = "The Awesome Stout";
|
||||
boolean active = false;
|
||||
Beer beer = new Beer(id, name, active, "");
|
||||
|
||||
@@ -58,4 +58,16 @@ public class CouchbaseTemplateViewListener extends DependencyInjectionTestExecut
|
||||
client.bucketManager().upsertDesignDocument(designDoc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(final TestContext testContext) throws Exception {
|
||||
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
|
||||
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
|
||||
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Beer b = new Beer("testbeer-" + i, "MyBeer" + i, true, "");
|
||||
template.remove(b);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.core;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.cluster.ClusterInfo;
|
||||
import com.couchbase.client.java.error.DocumentDoesNotExistException;
|
||||
import com.couchbase.client.java.view.*;
|
||||
|
||||
import org.springframework.data.couchbase.config.BeanNames;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class ReactiveCouchbaseTemplateViewListener 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) {
|
||||
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, "");
|
||||
template.save(b).subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndWaitForDesignDocs(Bucket client) {
|
||||
String mapFunction = "function (doc, meta) { if(doc._class == "
|
||||
+ "\"org.springframework.data.couchbase.core.ReactiveBeer\") { emit(doc.name, null); } }";
|
||||
View view = DefaultView.create("by_name", mapFunction);
|
||||
DesignDocument designDoc = DesignDocument.create("reactive_test_beers", Collections.singletonList(view));
|
||||
client.bucketManager().upsertDesignDocument(designDoc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(final TestContext testContext) throws Exception {
|
||||
Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET);
|
||||
ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO);
|
||||
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, "");
|
||||
template.remove(b).subscribe();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.core;
|
||||
|
||||
import static com.couchbase.client.java.query.Select.select;
|
||||
import static com.couchbase.client.java.query.dsl.Expression.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.PersistTo;
|
||||
import com.couchbase.client.java.ReplicateTo;
|
||||
import com.couchbase.client.java.document.RawJsonDocument;
|
||||
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
|
||||
import com.couchbase.client.java.error.DocumentDoesNotExistException;
|
||||
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
|
||||
import com.couchbase.client.java.query.N1qlParams;
|
||||
import com.couchbase.client.java.query.N1qlQuery;
|
||||
import com.couchbase.client.java.query.consistency.ScanConsistency;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
import com.couchbase.client.java.view.Stale;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
**/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
|
||||
@TestExecutionListeners(ReactiveCouchbaseTemplateViewListener.class)
|
||||
public class RxJavaCouchbaseTemplateTests {
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Autowired
|
||||
private Bucket client;
|
||||
|
||||
@Autowired
|
||||
private RxJavaCouchbaseOperations template;
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
|
||||
private void removeIfExist(String key) {
|
||||
template.remove(key).subscribe();
|
||||
}
|
||||
|
||||
private void removeCollectionIfExist(Collection<ReactiveBeer> beers) {
|
||||
template.remove(beers, PersistTo.MASTER, ReplicateTo.NONE)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveSimpleEntityCorrectly() throws Exception {
|
||||
String id = "reactivebeers:awesome-stout";
|
||||
removeIfExist(id);
|
||||
|
||||
String name = "The Awesome Stout";
|
||||
boolean active = false;
|
||||
ReactiveBeer beer = new ReactiveBeer(id, name, active, "");
|
||||
|
||||
template.save(beer)
|
||||
.subscribe();
|
||||
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
|
||||
assertNotNull(resultDoc);
|
||||
String result = resultDoc.content();
|
||||
assertNotNull(result);
|
||||
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
assertNotNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
|
||||
assertNull(resultConv.get("javaClass"));
|
||||
assertEquals("org.springframework.data.couchbase.core.ReactiveBeer", resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
|
||||
assertEquals(false, resultConv.get("is_active"));
|
||||
assertEquals("The Awesome Stout", resultConv.get("name"));
|
||||
removeIfExist(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveCollectionCorrectly() throws Exception {
|
||||
Collection<ReactiveBeer> beers = new ArrayList<>();
|
||||
String name = "The Awesome Stout";
|
||||
|
||||
for (int i=0; i < 10000; i++) {
|
||||
beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, ""));
|
||||
}
|
||||
removeCollectionIfExist(beers);
|
||||
template.save(beers).subscribe();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeDocument() {
|
||||
String id = "beers:to-delete-stout";
|
||||
ReactiveBeer beer = new ReactiveBeer(id, "", false, "");
|
||||
removeIfExist(id);
|
||||
|
||||
template.save(beer).subscribe();
|
||||
Object result = client.get(id);
|
||||
assertNotNull(result);
|
||||
|
||||
template.remove(beer).subscribe();
|
||||
result = client.get(id);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void storeListsAndMaps() {
|
||||
String id = "persons:lots-of-names";
|
||||
List<String> names = new ArrayList<String>();
|
||||
names.add("Michael");
|
||||
names.add("Thomas");
|
||||
names.add(null);
|
||||
List<Integer> votes = new LinkedList<Integer>();
|
||||
Map<String, Boolean> info1 = new HashMap<String, Boolean>();
|
||||
info1.put("foo", true);
|
||||
info1.put("bar", false);
|
||||
info1.put("nullValue", null);
|
||||
Map<String, Integer> info2 = new HashMap<String, Integer>();
|
||||
|
||||
ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2);
|
||||
|
||||
template.save(complex).toBlocking();
|
||||
assertNotNull(client.get(id));
|
||||
|
||||
ComplexPerson response = template.findById(id, ComplexPerson.class).toBlocking().single();
|
||||
assertEquals(names, response.getFirstnames());
|
||||
assertEquals(votes, response.getVotes());
|
||||
assertEquals(id, response.getId());
|
||||
assertEquals(info1, response.getInfo1());
|
||||
assertEquals(info2, response.getInfo2());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void validFindById() {
|
||||
String id = "reactive beers:findme-stout";
|
||||
String name = "Findme Stout";
|
||||
boolean active = true;
|
||||
ReactiveBeer beer = new ReactiveBeer(id, name, active, "");
|
||||
template.save(beer).subscribe();
|
||||
|
||||
ReactiveBeer found = template.findById(id, ReactiveBeer.class).toBlocking().single();
|
||||
|
||||
assertNotNull(found);
|
||||
assertEquals(id, found.getId());
|
||||
assertEquals(name, found.getName());
|
||||
assertEquals(active, found.getActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLoadAndMapViewDocs() {
|
||||
ViewQuery query = ViewQuery.from("reactive_test_beers", "by_name");
|
||||
query.stale(Stale.FALSE);
|
||||
|
||||
final List<ReactiveBeer> beers = template.findByView(query, ReactiveBeer.class).toList().toBlocking().single();
|
||||
assertTrue(beers.size() > 0);
|
||||
|
||||
for (ReactiveBeer beer : beers) {
|
||||
assertNotNull(beer.getId());
|
||||
assertNotNull(beer.getName());
|
||||
assertNotNull(beer.getActive());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQueryRaw() {
|
||||
N1qlQuery query = N1qlQuery.simple(select("name").from(i(client.name()))
|
||||
.where(x("name").isNotMissing()));
|
||||
|
||||
AsyncN1qlQueryResult queryResult = template.queryN1QL(query).toBlocking().single();
|
||||
assertNotNull(queryResult);
|
||||
assertTrue(queryResult.errors().toString(), queryResult.finalSuccess().toBlocking().single());
|
||||
assertFalse(queryResult.rows().toList().toBlocking().single().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQueryWithMapping() {
|
||||
FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1");
|
||||
FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2");
|
||||
template.save(Arrays.asList(ff1, ff2));
|
||||
|
||||
N1qlQuery query = N1qlQuery.simple(select(i("value")) //"value" is a n1ql keyword apparently
|
||||
.from(i(client.name()))
|
||||
.where(x("type").eq(s("fullFragment"))
|
||||
.and(x("criteria").gt(1))),
|
||||
|
||||
N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS));
|
||||
|
||||
List<Fragment> fragments = template.findByN1QLProjection(query, Fragment.class).toList().toBlocking().single();
|
||||
assertNotNull(fragments);
|
||||
assertFalse(fragments.isEmpty());
|
||||
assertEquals(1, fragments.size());
|
||||
assertEquals("test2", fragments.get(0).value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserialiseLongsAndInts() {
|
||||
final long longValue = new Date().getTime();
|
||||
final int intValue = new Random().nextInt();
|
||||
|
||||
template.save(new SimpleWithLongAndInt("simpleWithLong:simple", longValue, intValue)).toBlocking().single();
|
||||
SimpleWithLongAndInt document = template.findById("simpleWithLong:simple", SimpleWithLongAndInt.class).toBlocking().single();
|
||||
assertNotNull(document);
|
||||
assertEquals(longValue, document.getLongValue());
|
||||
assertEquals(intValue, document.getIntValue());
|
||||
|
||||
template.save(new SimpleWithLongAndInt("simpleWithLong:simple:other", intValue, intValue)).toBlocking().single();
|
||||
document = template.findById("simpleWithLong:simple:other", SimpleWithLongAndInt.class).toBlocking().single();
|
||||
assertNotNull(document);
|
||||
assertEquals(intValue, document.getLongValue());
|
||||
assertEquals(intValue, document.getIntValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserialiseEnums() {
|
||||
SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG);
|
||||
template.save(simpleWithEnum).toBlocking().single();
|
||||
simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class).toBlocking().single();
|
||||
assertNotNull(simpleWithEnum);
|
||||
assertEquals(simpleWithEnum.getType(), SimpleWithEnum.Type.BIG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeserialiseClass() {
|
||||
SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class);
|
||||
simpleWithClass.setValue("The dish ran away with the spoon.");
|
||||
template.save(simpleWithClass).toBlocking().single();
|
||||
simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class).toBlocking().single();
|
||||
assertNotNull(simpleWithClass);
|
||||
assertThat(simpleWithClass.getValue(), equalTo("The dish ran away with the spoon."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expiryWhenTouchOnReadDocument() throws InterruptedException {
|
||||
String id = "simple-doc-with-update-expiry-for-read";
|
||||
DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id);
|
||||
template.save(doc).subscribe();
|
||||
Thread.sleep(1500);
|
||||
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
|
||||
Thread.sleep(1500);
|
||||
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
|
||||
Thread.sleep(3000);
|
||||
assertNull(template.findById(id, DocumentWithTouchOnRead.class).toBlocking().single());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRetainOrderWhenQueryingViewOrdered() {
|
||||
ViewQuery q = ViewQuery.from("reactive_test_beers", "by_name");
|
||||
q.descending().includeDocsOrdered(true);
|
||||
|
||||
String prev = null;
|
||||
List<ReactiveBeer> beers = template.findByView(q, ReactiveBeer.class).toList().toBlocking().single();
|
||||
assertTrue(q.isIncludeDocs());
|
||||
assertTrue(q.isOrderRetained());
|
||||
assertEquals(RawJsonDocument.class, q.includeDocsTarget());
|
||||
for (ReactiveBeer beer : beers) {
|
||||
if (prev != null) {
|
||||
assertThat(beer.getName() + " not alphabetically < to " + prev, beer.getName().compareTo(prev) < 0);
|
||||
}
|
||||
prev = beer.getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sample document with just an id and property.
|
||||
*/
|
||||
@Document
|
||||
static class SimplePerson {
|
||||
|
||||
@Id
|
||||
private final String id;
|
||||
@Field
|
||||
private final String name;
|
||||
|
||||
public SimplePerson(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sample document that expires in 2 seconds.
|
||||
*/
|
||||
@Document(expiry = 2)
|
||||
static class DocumentWithExpiry {
|
||||
|
||||
@Id
|
||||
private final String id;
|
||||
|
||||
public DocumentWithExpiry(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sample document that expires in 2 seconds and touchOnRead set.
|
||||
*/
|
||||
@Document(expiry = 2, touchOnRead = true)
|
||||
static class DocumentWithTouchOnRead {
|
||||
|
||||
@Id
|
||||
private final String id;
|
||||
|
||||
public DocumentWithTouchOnRead(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
static class ComplexPerson {
|
||||
|
||||
@Id
|
||||
private final String id;
|
||||
@Field
|
||||
private final List<String> firstnames;
|
||||
@Field
|
||||
private final List<Integer> votes;
|
||||
|
||||
@Field
|
||||
private final Map<String, Boolean> info1;
|
||||
@Field
|
||||
private final Map<String, Integer> info2;
|
||||
|
||||
public ComplexPerson(String id, List<String> firstnames,
|
||||
List<Integer> votes, Map<String, Boolean> info1,
|
||||
Map<String, Integer> info2) {
|
||||
this.id = id;
|
||||
this.firstnames = firstnames;
|
||||
this.votes = votes;
|
||||
this.info1 = info1;
|
||||
this.info2 = info2;
|
||||
}
|
||||
|
||||
List<String> getFirstnames() {
|
||||
return firstnames;
|
||||
}
|
||||
|
||||
List<Integer> getVotes() {
|
||||
return votes;
|
||||
}
|
||||
|
||||
Map<String, Boolean> getInfo1() {
|
||||
return info1;
|
||||
}
|
||||
|
||||
Map<String, Integer> getInfo2() {
|
||||
return info2;
|
||||
}
|
||||
|
||||
String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
static class SimpleWithLongAndInt {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private long longValue;
|
||||
private int intValue;
|
||||
|
||||
SimpleWithLongAndInt(final String id, final long longValue, int intValue) {
|
||||
this.id = id;
|
||||
this.longValue = longValue;
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
long getLongValue() {
|
||||
return longValue;
|
||||
}
|
||||
|
||||
void setLongValue(final long value) {
|
||||
this.longValue = value;
|
||||
}
|
||||
|
||||
public int getIntValue() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
public void setIntValue(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
}
|
||||
|
||||
static class SimpleWithEnum {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private enum Type {
|
||||
BIG
|
||||
}
|
||||
|
||||
Type type;
|
||||
|
||||
SimpleWithEnum(final String id, final Type type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void setId(final String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
void setType(final Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
static class SimpleWithClass {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private Class<Integer> integerClass;
|
||||
|
||||
private String value;
|
||||
|
||||
SimpleWithClass(final String id, final Class<Integer> integerClass) {
|
||||
this.id = id;
|
||||
this.integerClass = integerClass;
|
||||
}
|
||||
|
||||
String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void setId(final String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
Class<Integer> getIntegerClass() {
|
||||
return integerClass;
|
||||
}
|
||||
|
||||
void setIntegerClass(final Class<Integer> integerClass) {
|
||||
this.integerClass = integerClass;
|
||||
}
|
||||
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
void setValue(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class VersionedClass {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
|
||||
private String field;
|
||||
|
||||
VersionedClass(String id, String field) {
|
||||
this.id = id;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VersionedClass{" +
|
||||
"id='" + id + '\'' +
|
||||
", version=" + version +
|
||||
", field='" + field + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
static class FullFragment {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private long criteria;
|
||||
|
||||
private String type;
|
||||
|
||||
private String value;
|
||||
|
||||
public FullFragment(String id, long criteria, String type, String value) {
|
||||
this.id = id;
|
||||
this.criteria = criteria;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setCriteria(long criteria) {
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class Fragment {
|
||||
public String value;
|
||||
}
|
||||
}
|
||||
@@ -134,9 +134,9 @@ public class CouchbaseRepositoryViewTests {
|
||||
String highKey = "uname-11";
|
||||
List<String> keys = Arrays.asList(lowKey, middleKey, highKey);
|
||||
|
||||
User u1 = repository.findByUsernameIs(lowKey);
|
||||
User u2 = repository.findByUsernameIs(middleKey);
|
||||
User u3 = repository.findByUsernameIs(highKey);
|
||||
User u1 = repository.findByUsernameIs(lowKey).get(0);
|
||||
User u2 = repository.findByUsernameIs(middleKey).get(0);
|
||||
User u3 = repository.findByUsernameIs(highKey).get(0);
|
||||
|
||||
|
||||
List<User> in = repository.findAllByUsernameIn(keys);
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface CustomUserRepository extends CouchbaseRepository<User, String>
|
||||
long countByUsernameGreaterThanEqualAndUsernameLessThan(String lowBound, String highBound);
|
||||
|
||||
@View(viewName = "customFindByNameView")
|
||||
User findByUsernameIs(String lowKey);
|
||||
List<User> findByUsernameIs(String lowKey);
|
||||
|
||||
@View(viewName = "customFindByNameView")
|
||||
List<User> findAllByUsernameIn(List<String> keys);
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.ReactiveIntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* This tests ReactiveSortingRepository features in the Couchbase connector.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
|
||||
@TestExecutionListeners(PartyPopulatorListener.class)
|
||||
public class ReactiveN1qlCouchbaseRepositoryTests {
|
||||
|
||||
@Autowired
|
||||
private ReactiveRepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private ReactivePartySortingRepository repository;
|
||||
|
||||
private ReactivePartyRepository partyRepository;
|
||||
|
||||
private ItemRepository itemRepository;
|
||||
|
||||
private final String KEY_PARTY = "ReactiveParty1";
|
||||
private final String KEY_ITEM = "ReactiveItem1";
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(ReactivePartySortingRepository.class);
|
||||
partyRepository = factory.getRepository(ReactivePartyRepository.class);
|
||||
itemRepository = factory.getRepository(ItemRepository.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
try { itemRepository.delete(KEY_ITEM); } catch (DataRetrievalFailureException e) {}
|
||||
try { partyRepository.delete(KEY_PARTY); } catch (DataRetrievalFailureException e) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindAllWithSort() {
|
||||
Iterable<Party> allByAttendanceDesc = repository.findAll(new Sort(Sort.Direction.DESC, "attendees")).collectList().block();
|
||||
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(new Sort(Sort.Direction.DESC, "desc")).collectList().block();
|
||||
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(new Sort(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase())).collectList().block();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
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.repository.query.Param;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@ViewIndexed(designDoc = "party", viewName = "all")
|
||||
@N1qlSecondaryIndexed(indexName = "party")
|
||||
public interface ReactivePartyRepository extends ReactiveCouchbaseRepository<Party, String> {
|
||||
|
||||
Flux<Party> findByAttendeesGreaterThanEqual(int minAttendees);
|
||||
|
||||
Flux<Party> findByEventDateIs(Date targetDate);
|
||||
|
||||
@View(designDocument = "party", viewName = "byDate")
|
||||
Flux<Party> findFirst3ByEventDateGreaterThanEqual(Date targetDate);
|
||||
|
||||
Flux<Object> findAllByDescriptionNotNull();
|
||||
|
||||
Mono<Long> countAllByDescriptionNotNull();
|
||||
|
||||
@Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
Mono<Long> findMaxAttendees();
|
||||
|
||||
@Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
Mono<String> findSomeString();
|
||||
|
||||
@Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
|
||||
Mono<Long> countCustomPlusFive();
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
|
||||
Mono<Long> countCustom();
|
||||
|
||||
@Query("SELECT 1 = 1")
|
||||
Mono<Boolean> justABoolean();
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" +
|
||||
" AND `desc` NOT LIKE '%' || $excluded || '%'")
|
||||
Flux<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 || '%'")
|
||||
Flux<Party> findAllWithPositionalParams(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\\\"\"")
|
||||
Flux<Party> findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min);
|
||||
|
||||
Flux<Party> findByDescriptionOrName(String description, String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public interface ReactivePartySortingRepository extends ReactiveCouchbaseSortingRepository<Party, String> {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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 org.springframework.data.annotation.Id;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class ReactiveUser {
|
||||
|
||||
@Id
|
||||
private final String key;
|
||||
|
||||
private final String username;
|
||||
|
||||
private final int age;
|
||||
|
||||
public ReactiveUser(String key, String username, int age) {
|
||||
this.key = key;
|
||||
this.username = username;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ReactiveUser user = (ReactiveUser) o;
|
||||
return key.equals(user.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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 java.util.List;
|
||||
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public interface ReactiveUserRepository extends ReactiveCouchbaseRepository<ReactiveUser, String> {
|
||||
|
||||
@View(designDocument = "user", viewName = "all")
|
||||
Flux<ReactiveUser> customViewQuery(ViewQuery query);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE username = $1")
|
||||
Flux<ReactiveUser> findByUsername(String username);
|
||||
|
||||
@Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1")
|
||||
Flux<ReactiveUser> findByUsernameBadSelect(String username);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE username LIKE '%-#{3 + 1}'")
|
||||
Flux<ReactiveUser> findByUsernameWithSpelAndPlaceholder();
|
||||
|
||||
@Query
|
||||
Flux<ReactiveUser> findByUsernameRegexAndUsernameIn(String regex, List<String> sample);
|
||||
|
||||
Flux<ReactiveUser> findByUsernameContains(String contains);
|
||||
|
||||
Flux<ReactiveUser> findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
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.View;
|
||||
import org.springframework.data.couchbase.config.BeanNames;
|
||||
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class SimpleReactiveCouchbaseRepositoryListener 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) {
|
||||
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ReactiveUser u = new ReactiveUser("reactivetestuser-" + i, "reactiveuname-" + i, i);
|
||||
template.save(u, PersistTo.MASTER, ReplicateTo.NONE).subscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void createAndWaitForDesignDocs(Bucket client) {
|
||||
String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository." +
|
||||
"ReactiveUser\") { emit(null, null); } }";
|
||||
View view = DefaultView.create("all", mapFunction, "_count");
|
||||
List<View> views = Collections.singletonList(view);
|
||||
DesignDocument designDoc = DesignDocument.create("reactiveUser", views);
|
||||
client.bucketManager().upsertDesignDocument(designDoc);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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 com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.error.DocumentDoesNotExistException;
|
||||
import com.couchbase.client.java.view.Stale;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
|
||||
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
|
||||
@TestExecutionListeners(SimpleReactiveCouchbaseRepositoryListener.class)
|
||||
public class SimpleReactiveCouchbaseRepositoryTests {
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Autowired
|
||||
private Bucket client;
|
||||
|
||||
@Autowired
|
||||
private ReactiveRepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private ReactiveUserRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(ReactiveUserRepository.class);
|
||||
}
|
||||
|
||||
private void remove(String key) {
|
||||
try {
|
||||
client.remove(key);
|
||||
} catch (DocumentDoesNotExistException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleCrud() {
|
||||
String key = "my_unique_user_key";
|
||||
ReactiveUser instance = new ReactiveUser(key, "foobar", 22);
|
||||
repository.save(instance).block();
|
||||
|
||||
ReactiveUser found = repository.findOne(key).block();
|
||||
assertEquals(instance.getKey(), found.getKey());
|
||||
assertEquals(instance.getUsername(), found.getUsername());
|
||||
|
||||
assertTrue(repository.exists(key).block());
|
||||
repository.delete(found).block();
|
||||
|
||||
assertNull(repository.findOne(key).block());
|
||||
assertFalse(repository.exists(key).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
/**
|
||||
* This test uses/assumes a default viewName called "all" that is configured on Couchbase.
|
||||
*/
|
||||
public void shouldFindAll() {
|
||||
// do a non-stale query to populate data for testing.
|
||||
client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE));
|
||||
|
||||
List<ReactiveUser> allUsers = repository.findAll().collectList().block();
|
||||
int size = 0;
|
||||
for (ReactiveUser u : allUsers) {
|
||||
size++;
|
||||
assertNotNull(u.getKey());
|
||||
assertNotNull(u.getUsername());
|
||||
}
|
||||
assertEquals(100, size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCount() {
|
||||
// do a non-stale query to populate data for testing.
|
||||
client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE));
|
||||
|
||||
assertEquals("100", repository.count().block().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindByUsernameUsingN1ql() {
|
||||
ReactiveUser user = repository.findByUsername("reactiveuname-1").single().block();
|
||||
assertNotNull(user);
|
||||
assertEquals("reactivetestuser-1", user.getKey());
|
||||
assertEquals("reactiveuname-1", user.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailFindByUsernameWithNoIdOrCas() {
|
||||
try {
|
||||
ReactiveUser user = repository.findByUsernameBadSelect("reactiveuname-1").single().block();
|
||||
fail("shouldFailFindByUsernameWithNoIdOrCas");
|
||||
} catch (CouchbaseQueryExecutionException e) {
|
||||
assertTrue("_ID expected in exception " + e, e.getMessage().contains("_ID"));
|
||||
assertTrue("_CAS expected in exception " + e, e.getMessage().contains("_CAS"));
|
||||
} catch (Exception e) {
|
||||
fail("CouchbaseQueryExecutionException expected");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindFromUsernameInlineWithSpelParsing() {
|
||||
ReactiveUser user = repository.findByUsernameWithSpelAndPlaceholder().take(1).blockLast();
|
||||
assertNotNull(user);
|
||||
assert(user.getUsername().startsWith("reactive"));
|
||||
assert(user.getUsername().startsWith("reactive"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindFromDeriveQueryWithRegexpAndIn() {
|
||||
ReactiveUser user = repository.findByUsernameRegexAndUsernameIn("reactiveuname-[123]", Arrays.asList("reactiveuname-2", "reactiveuname-4")).take(1).blockLast();
|
||||
assertNotNull(user);
|
||||
assertEquals("reactivetestuser-2", user.getKey());
|
||||
assertEquals("reactiveuname-2", user.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindContainsWithoutAnnotation() {
|
||||
List<ReactiveUser> users = repository.findByUsernameContains("reactive").collectList().block();
|
||||
assertNotNull(users);
|
||||
assertFalse(users.isEmpty());
|
||||
for (ReactiveUser user : users) {
|
||||
assertTrue(user.getUsername().startsWith("reactive"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDefaultToN1qlQueryDerivation() {
|
||||
try {
|
||||
ReactiveUser u = repository.findByUsernameNear("london").single().block();
|
||||
fail("Expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (!e.getMessage().contains("N1QL")) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -34,13 +34,13 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
@View(designDocument = "user", viewName = "all")
|
||||
Iterable<User> customViewQuery(ViewQuery query);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE username = $1")
|
||||
@Query("#{#n1ql.selectEntity} WHERE username = $1 and #{#n1ql.filter}")
|
||||
User findByUsername(String username);
|
||||
|
||||
@Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1")
|
||||
@Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1 and #{#n1ql.filter} ")
|
||||
User findByUsernameBadSelect(String username);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE username LIKE '%-#{3 + 1}'")
|
||||
@Query("#{#n1ql.selectEntity} WHERE username LIKE '%-#{3 + 1}' and #{#n1ql.filter}'")
|
||||
User findByUsernameWithSpelAndPlaceholder();
|
||||
|
||||
@Query
|
||||
|
||||
Reference in New Issue
Block a user