Revert to single-module. (#1583)

Closed #1582.
This commit is contained in:
Michael Reiche
2022-10-09 21:08:11 -10:00
committed by GitHub
parent 8d99a39a49
commit b872714be1
471 changed files with 317 additions and 1090 deletions

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2022 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.cache;
import java.io.Serializable;
/**
* This is a standalone class (vs. inner) to allow Serialization of all fields to work.
* If it was an inner class of CouchbaseCacheIntegrationTests, then it would have a
* this$0 field = CouchbaseCacheIntegrationTests and would not serialize.
*
* @author Michael Reiche
*/
class CacheUser implements Serializable {
// private static final long serialVersionUID = 8817717605659870262L;
String firstname;
String lastname;
String id;
public CacheUser(String id, String firstname, String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
public String getId() {
return id;
}
// define equals for assertEquals()
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof CacheUser)) {
return false;
}
CacheUser other = (CacheUser) o;
if (id == null && other.id != null) {
return false;
}
if (firstname == null && other.firstname != null) {
return false;
}
if (lastname == null && other.lastname != null) {
return false;
}
return id.equals(other.id) && firstname.equals(other.firstname) && lastname.equals(other.lastname);
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("CacheUser: { id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + "}");
return sb.toString();
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2022 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.cache;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
/**
* CouchbaseCache tests Theses tests rely on a cb server running.
*
* @author Michael Reiche
*/
@IgnoreWhen(clusterTypes = ClusterType.MOCKED, missesCapabilities = { Capabilities.COLLECTIONS })
class CouchbaseCacheCollectionIntegrationTests extends CollectionAwareIntegrationTests {
volatile CouchbaseCache cache;
@BeforeEach
@Override
public void beforeEach() {
super.beforeEach();
cache = CouchbaseCacheManager.create(couchbaseTemplate.getCouchbaseClientFactory()).createCouchbaseCache("myCache",
CouchbaseCacheConfiguration.defaultCacheConfig().collection("my_collection"));
cache.clear();
}
@Test
void cachePutGet() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
assertNull(cache.get(user1.getId())); // was not put -> cacheMiss
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
assertEquals(user1, cache.get(user1.getId()).get()); // get user1
assertEquals(user2, cache.get(user2.getId()).get()); // get user2
}
@Test
void cacheEvict() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
cache.evict(user1.getId()); // evict user1
assertNull(cache.get(user1.getId())); // get user1 -> not present
assertEquals(user2, cache.get(user2.getId()).get()); // get user2 -> present
}
@Test
void cacheClear() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
cache.clear();
assertNull(cache.get(user1.getId())); // get user1 -> not present
assertNull(cache.get(user2.getId())); // get user2 -> not present
}
@Test
void cacheHitMiss() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
assertNull(cache.get(user2.getId())); // get user2 -> cacheMiss
cache.put(user1.getId(), null); // cache a null
assertNotNull(cache.get(user1.getId())); // cacheHit null
assertNull(cache.get(user1.getId()).get()); // fetch cached null
}
@Test
void cachePutIfAbsent() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
assertNull(cache.putIfAbsent(user1.getId(), user1)); // should put user1, return null
assertEquals(user1, cache.putIfAbsent(user1.getId(), user2).get()); // should not put user2, should return user1
assertEquals(user1, cache.get(user1.getId()).get()); // user1.getId() is still user1
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2022 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.cache;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
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.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* CouchbaseCache tests Theses tests rely on a cb server running.
*
* @author Michael Reiche
*/
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(Config.class)
class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
volatile CouchbaseCache cache;
@Autowired CouchbaseCacheManager cacheManager; // autowired not working
@Autowired UserRepository userRepository; // autowired not working
@BeforeEach
@Override
public void beforeEach() {
super.beforeEach();
cache = CouchbaseCacheManager.create(couchbaseTemplate.getCouchbaseClientFactory()).createCouchbaseCache("myCache",
CouchbaseCacheConfiguration.defaultCacheConfig());
cache.clear();
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
cacheManager = ac.getBean(CouchbaseCacheManager.class);
userRepository = ac.getBean(UserRepository.class);
}
@AfterEach
@Override
public void afterEach() {
cache.clear();
super.afterEach();
}
@Test
void cachePutGet() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
assertNull(cache.get(user1.getId())); // was not put -> cacheMiss
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
assertEquals(user1, cache.get(user1.getId()).get()); // get user1
assertEquals(user2, cache.get(user2.getId()).get()); // get user2
}
@Test
void cacheable() {
User user = new User("cache_92", "Dave", "Wilson");
cacheManager.getCache("mySpringCache").clear();
userRepository.save(user);
long t0 = System.currentTimeMillis();
List<User> users = userRepository.getByFirstname(user.getFirstname());
assert (System.currentTimeMillis() - t0 > 1000 * 5);
t0 = System.currentTimeMillis();
users = userRepository.getByFirstname(user.getFirstname());
assert (System.currentTimeMillis() - t0 < 100);
}
@Test
void cacheEvict() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
cache.evict(user1.getId()); // evict user1
assertNull(cache.get(user1.getId())); // get user1 -> not present
assertEquals(user2, cache.get(user2.getId()).get()); // get user2 -> present
}
@Test
void cacheClear() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
cache.put(user1.getId(), user1); // put user1
cache.put(user2.getId(), user2); // put user2
cache.clear();
assertNull(cache.get(user1.getId())); // get user1 -> not present
assertNull(cache.get(user2.getId())); // get user2 -> not present
}
@Test
void cachePutIfAbsent() {
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
assertNull(cache.putIfAbsent(user1.getId(), user1)); // should put user1, return null
assertEquals(user1, cache.putIfAbsent(user1.getId(), user2).get()); // should not put user2, should return user1
assertEquals(user1, cache.get(user1.getId()).get()); // user1.getId() is still user1
}
@Test // this WORKS
public void clearWithDelayOk() throws InterruptedException {
cache.put("KEY", "VALUE");
Thread.sleep(50); // give main index time to update
cache.clear();
assertNull(cache.get("KEY"));
}
@Test
public void noOpt() {}
}

View File

@@ -0,0 +1,458 @@
/*
* Copyright 2012-2022 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.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
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 java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.core.ExecutableFindByIdOperation.ExecutableFindById;
import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById;
import org.springframework.data.couchbase.core.ExecutableReplaceByIdOperation.ExecutableReplaceById;
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
import org.springframework.data.couchbase.core.support.OneAndAllId;
import org.springframework.data.couchbase.core.support.WithDurability;
import org.springframework.data.couchbase.core.support.WithExpiry;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.PersonValue;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserAnnotated;
import org.springframework.data.couchbase.domain.UserAnnotated2;
import org.springframework.data.couchbase.domain.UserAnnotated3;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* KV tests Theses tests rely on a cb server running.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(Config.class)
class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeEach
@Override
public void beforeEach() {
couchbaseTemplate.removeByQuery(User.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated2.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated3.class).all();
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
@Test
void findByIdWithExpiry() {
try {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
Collection<User> upserts = (Collection<User>) couchbaseTemplate.upsertById(User.class)
.all(Arrays.asList(user1, user2));
User foundUser = couchbaseTemplate.findById(User.class).withExpiry(Duration.ofSeconds(1)).one(user1.getId());
user1.setVersion(foundUser.getVersion());// version will have changed
assertEquals(user1, foundUser);
int tries = 0;
Collection<User> foundUsers;
do {
sleepSecs(1);
foundUsers = (Collection<User>) couchbaseTemplate.findById(User.class)
.all(Arrays.asList(user1.getId(), user2.getId()));
} while (tries++ < 10 && foundUsers.size() != 1 && !user2.equals(foundUsers.iterator().next()));
assertEquals(1, foundUsers.size(), "should have found exactly 1 user");
assertEquals(user2, foundUsers.iterator().next());
} finally {
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
}
@Test
void upsertAndFindById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
assertEquals(user, modified);
// create a new object so that the object returned by replaceById() is a different object from the original user
// don't need to copy the ModifiedDate/ModifiedTime as they are not read and are overwritten.
User modifying = new User(user.getId(), user.getFirstname(), user.getLastname());
modifying.setCreatedDate(user.getCreatedDate());
modifying.setCreatedBy(user.getCreatedBy());
modifying.setVersion(user.getVersion());
modified = couchbaseTemplate.replaceById(User.class).one(modifying);
assertEquals(modifying, modified);
if (user == modified) {
throw new RuntimeException(" user == modified ");
}
assertNotEquals(user, modified);
assertEquals(NaiveAuditorAware.AUDITOR, modified.getCreatedBy());
assertEquals(NaiveAuditorAware.AUDITOR, modified.getLastModifiedBy());
assertNotEquals(0, modified.getCreatedDate());
assertNotEquals(0, modified.getLastModifiedDate());
// The FixedDateTimeService of the AuditingDateTimeProvider will guarantee these are equal
assertEquals(user.getLastModifiedDate(), modified.getLastModifiedDate());
User badUser = new User(user.getId(), user.getFirstname(), user.getLastname());
badUser.setVersion(12345678);
assertThrows(OptimisticLockingFailureException.class, () -> couchbaseTemplate.replaceById(User.class).one(badUser));
User found = couchbaseTemplate.findById(User.class).one(user.getId());
assertEquals(modified, found);
couchbaseTemplate.removeById().one(user.getId());
}
@Test
void findProjected() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
couchbaseTemplate.insertById(User.class).one(user);
User found = couchbaseTemplate.findById(User.class).project(new String[] { "firstname" }).one(user.getId());
System.err.println(found);
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
void findProjecting() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
couchbaseTemplate.insertById(User.class).one(user);
List<User> found = couchbaseTemplate.findByQuery(User.class).project(new String[] { "firstname" })
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS)).all();
assertEquals(1, found.size());
assertNotEquals(user, found.get(0), "should have found this document");
assertEquals(user.getFirstname(), found.get(0).getFirstname(), "firstname should match");
assertNull(found.get(0).getLastname(), "lastname should be null");
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
void findProjectingPath() {
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user.setRoles(Arrays.asList("role1", "role2"));
Address address = new Address();
address.setStreet("1234 Olcott Street");
address.setCity("Santa Clara");
user.setAddress(address);
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
couchbaseTemplate.upsertById(UserSubmission.class).one(user);
assertThrows(CouchbaseException.class, () -> couchbaseTemplate.findByQuery(UserSubmission.class)
.project(new String[] { "address.street" }).withConsistency(QueryScanConsistency.REQUEST_PLUS).all());
List<UserSubmission> found = couchbaseTemplate.findByQuery(UserSubmission.class).project(new String[] { "address" })
.withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
assertEquals(found.size(), 1);
assertEquals(found.get(0).getAddress(), address);
assertNull(found.get(0).getUsername(), "username should have been null");
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
void withDurability()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
// insert, replace, upsert
for (OneAndAllEntity<User> operator : new OneAndAllEntity[] { couchbaseTemplate.insertById(clazz),
couchbaseTemplate.replaceById(clazz), couchbaseTemplate.upsertById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
operator = (OneAndAllEntity) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
ReplicateTo.NONE);
}
// if replace, we need to insert a document to replace
if (operator instanceof ExecutableReplaceById) {
couchbaseTemplate.insertById(User.class).one(user);
}
// call to insert/replace/update
User returned = null;
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
for (int i = 1; i != 5; i++) {
try {
returned = (User) operator.one(user);
break;
} catch (Exception ofe) {
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
}
sleepSecs(1);
}
}
assertEquals(user, returned);
User found = couchbaseTemplate.findById(User.class).one(user.getId());
assertEquals(user, found);
if (operator instanceof ExecutableReplaceById) {
couchbaseTemplate.removeById().withDurability(PersistTo.ACTIVE, ReplicateTo.NONE).one(user.getId());
User removed = (User) couchbaseTemplate.findById(user.getClass()).one(user.getId());
assertNull(removed, "found should have been null as document should be removed");
}
}
}
@Test
void withExpiryAndExpiryAnnotation()
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// ( withExpiry()<User>, expiry=1<UserAnnotated>, expiryExpression=${myExpiry}<UserAnnotated2> ) X ( insert,
// replace, upsert )
Set<User> users = new HashSet<>(); // set of all documents we will insert
// Entity classes
for (Class<?> clazz : new Class[] { User.class, UserAnnotated.class, UserAnnotated2.class, UserAnnotated3.class }) {
// insert, replace, upsert
for (Object operator : new Object[] { couchbaseTemplate.insertById(clazz), couchbaseTemplate.replaceById(clazz),
couchbaseTemplate.upsertById(clazz), couchbaseTemplate.findById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an expiry annotation
operator = ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(1));
} else if (clazz.equals(UserAnnotated3.class)) { // override the expiry from the annotation with no expiry
operator = ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(0));
}
// if replace, remove or find, we need to insert a document first
if (operator instanceof ExecutableReplaceById || operator instanceof ExecutableRemoveById
|| operator instanceof ExecutableFindById) {
user = couchbaseTemplate.insertById(User.class).one(user);
}
// call to insert/replace/update/find
User returned = operator instanceof OneAndAllEntity ? ((OneAndAllEntity<User>) operator).one(user)
: ((OneAndAllId<User>) operator).one(user.getId());
if (operator instanceof OneAndAllId) { // the user.version won't be updated
user.setVersion(returned.getVersion());
}
assertEquals(user, returned);
users.add(user);
}
}
// check that they are gone after a few seconds.
int tries = 0;
List<String> errorList = new LinkedList<>();
do {
sleepSecs(1);
for (User user : users) {
errorList = new LinkedList<>();
User found = couchbaseTemplate.findById(user.getClass()).one(user.getId());
if (user.getId().endsWith(UserAnnotated3.class.getSimpleName())) {
if (found == null) {
errorList.add("\nfound should be non null as it was set to have no expiry " + user.getId());
}
} else {
if (found != null) {
errorList.add("\nfound should have been null as document should be expired " + user.getId());
}
}
if (found != null) {
couchbaseTemplate.removeById(user.getClass()).one(user.getId());
}
}
} while (tries++ < 10 && !errorList.isEmpty());
if (!errorList.isEmpty()) {
throw new RuntimeException(errorList.toString());
}
}
@Test
void findDocWhichDoesNotExist() {
assertNull(couchbaseTemplate.findById(User.class).one(UUID.randomUUID().toString()));
}
@Test
void upsertAndReplaceById() {
User user = new User(UUID.randomUUID().toString(), "firstname_upsertAndReplaceById", "lastname");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
assertEquals(user, modified);
User toReplace = new User(modified.getId(), "some other", "lastname");
couchbaseTemplate.replaceById(User.class).one(toReplace);
User loaded = couchbaseTemplate.findById(User.class).one(toReplace.getId());
assertEquals("some other", loaded.getFirstname());
couchbaseTemplate.removeById().one(toReplace.getId());
}
@Test
void upsertAndRemoveById() {
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
assertEquals(user, modified);
RemoveResult removeResult = couchbaseTemplate.removeById().one(user.getId());
assertEquals(user.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
assertNull(couchbaseTemplate.findById(User.class).one(user.getId()));
}
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
assertEquals(user, modified);
// careful now - user and modified are the same object. The object has the new cas (@Version version)
Long savedCas = modified.getVersion();
modified.setVersion(123);
assertThrows(OptimisticLockingFailureException.class, () -> couchbaseTemplate.removeById()
.withCas(reactiveCouchbaseTemplate.support().getCas(modified)).one(modified.getId()));
modified.setVersion(savedCas);
couchbaseTemplate.removeById().withCas(reactiveCouchbaseTemplate.support().getCas(modified))
.one(modified.getId());
}
}
@Test
void insertById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = couchbaseTemplate.insertById(User.class).one(user);
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(User.class).one(user));
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
void insertByIdwithDurability() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = null;
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
for (int i = 1; i != 5; i++) {
try {
inserted = couchbaseTemplate.insertById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.NONE)
.one(user);
break;
} catch (Exception ofe) {
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
}
sleepSecs(1);
}
}
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(User.class).one(user));
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
void existsById() {
String id = UUID.randomUUID().toString();
assertFalse(couchbaseTemplate.existsById().one(id));
User user = new User(id, "firstname", "lastname");
User inserted = couchbaseTemplate.insertById(User.class).one(user);
assertEquals(user, inserted);
assertTrue(couchbaseTemplate.existsById().one(id));
couchbaseTemplate.removeById(User.class).one(user.getId());
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindImmutableById() {
PersonValue personValue = new PersonValue(UUID.randomUUID().toString(), 123, "408", "l");
PersonValue inserted = null;
PersonValue upserted = null;
PersonValue replaced = null;
inserted = couchbaseTemplate.insertById(PersonValue.class).one(personValue);
assertNotEquals(0, inserted.getVersion());
PersonValue foundInserted = couchbaseTemplate.findById(PersonValue.class).one(inserted.getId());
assertNotNull(foundInserted, "inserted personValue not found");
assertEquals(inserted, foundInserted);
// upsert will insert
couchbaseTemplate.removeById().one(inserted.getId());
upserted = couchbaseTemplate.upsertById(PersonValue.class).one(inserted);
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted = couchbaseTemplate.findById(PersonValue.class).one(upserted.getId());
assertNotNull(foundUpserted, "upserted personValue not found");
assertEquals(upserted, foundUpserted);
// upsert will replace
upserted = couchbaseTemplate.upsertById(PersonValue.class).one(inserted);
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted2 = couchbaseTemplate.findById(PersonValue.class).one(upserted.getId());
assertNotNull(foundUpserted2, "upserted personValue not found");
assertEquals(upserted, foundUpserted2);
replaced = couchbaseTemplate.replaceById(PersonValue.class).one(upserted);
assertNotEquals(0, replaced.getVersion());
PersonValue foundReplaced = couchbaseTemplate.findById(PersonValue.class).one(replaced.getId());
assertNotNull(foundReplaced, "replaced personValue not found");
assertEquals(replaced, foundReplaced);
couchbaseTemplate.removeById(PersonValue.class).one(replaced.getId());
}
private void sleepSecs(int i) {
try {
Thread.sleep(i * 1000);
} catch (InterruptedException ie) {}
}
}

View File

@@ -0,0 +1,832 @@
/*
* Copyright 2021-2022 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.core;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.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 java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
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.CollectionsConfig;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.domain.UserJustLastName;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.domain.UserSubmissionProjected;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.UnambiguousTimeoutException;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetOptions;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions;
/**
* Query tests Theses tests rely on a cb server running This class tests collection support with
* inCollection(collection), inScope(scope) and withOptions(options). Testing without collections could also be done by
* this class simply by using scopeName = null and collectionName = null
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(CollectionsConfig.class)
class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
Airport vie = new Airport("airports::vie", "vie", "loww");
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
// no-op
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(scopeName)
.inCollection(collectionName).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(scopeName)
.inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).all();
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
// query with REQUEST_PLUS to ensure that the remove has completed.
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
// then call the super method
super.afterEach();
}
@Test
void findByQueryAll() {
try {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).all();
for (User u : foundUsers) {
if (!(u.equals(user1) || u.equals(user2))) {
// somebody didn't clean up after themselves.
couchbaseTemplate.removeById().inCollection(collectionName).one(u.getId());
}
}
assertEquals(2, foundUsers.size());
TemporalAccessor auditTime = new AuditingDateTimeProvider().getNow().get();
long auditMillis = Instant.from(auditTime).toEpochMilli();
String auditUser = new NaiveAuditorAware().getCurrentAuditor().get();
for (User u : foundUsers) {
assertTrue(u.equals(user1) || u.equals(user2));
assertEquals(auditUser, u.getCreatedBy());
assertEquals(auditMillis, u.getCreatedDate());
assertEquals(auditUser, u.getLastModifiedBy());
assertEquals(auditMillis, u.getLastModifiedDate());
}
couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId());
reactiveCouchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId()).block();
} finally {
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
}
User usery = couchbaseTemplate.findById(User.class).inCollection(collectionName).one("userx");
assertNull(usery, "usery should be null");
User userz = reactiveCouchbaseTemplate.findById(User.class).inCollection(collectionName).one("userx").block();
assertNull(userz, "userz should be null");
}
@Test
void findByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).matching(specialUsers).all();
assertEquals(1, foundUsers.size());
}
@Test
void findByMatchingQueryProjected() {
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user.setRoles(Arrays.asList("role1", "role2"));
Address address = new Address();
address.setStreet("1234 Olcott Street");
user.setAddress(address);
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
user.setCourses(Arrays.asList(new Course(UUID.randomUUID().toString(), user.getId(), "581"),
new Course(UUID.randomUUID().toString(), user.getId(), "777")));
couchbaseTemplate.upsertById(UserSubmission.class).inCollection(collectionName).one(user);
Query daveUsers = new Query(QueryCriteria.where("username").like("dave"));
final List<UserSubmissionProjected> foundUserSubmissions = couchbaseTemplate.findByQuery(UserSubmission.class)
.as(UserSubmissionProjected.class).withConsistency(REQUEST_PLUS).inCollection(collectionName)
.matching(daveUsers).all();
assertEquals(1, foundUserSubmissions.size());
assertEquals(user.getUsername(), foundUserSubmissions.get(0).getUsername());
assertEquals(user.getId(), foundUserSubmissions.get(0).getId());
assertEquals(user.getCourses(), foundUserSubmissions.get(0).getCourses());
assertEquals(user.getAddress(), foundUserSubmissions.get(0).getAddress());
couchbaseTemplate.removeByQuery(UserSubmission.class).inCollection(collectionName).all();
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
final List<UserJustLastName> foundUsers = couchbaseTemplate.findByQuery(User.class).as(UserJustLastName.class)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).matching(specialUsers).all();
assertEquals(1, foundUsers.size());
final List<UserJustLastName> foundUsersReactive = reactiveCouchbaseTemplate.findByQuery(User.class)
.as(UserJustLastName.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).matching(specialUsers)
.all().collectList().block();
assertEquals(1, foundUsersReactive.size());
couchbaseTemplate.removeByQuery(UserSubmission.class).withConsistency(REQUEST_PLUS).all();
couchbaseTemplate.removeByQuery(UserSubmission.class).withConsistency(REQUEST_PLUS).all();
}
@Test
void removeByQueryAll() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).inScope(scopeName).inCollection(collectionName)
.all(Arrays.asList(user1, user2));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user2.getId()));
List<RemoveResult> result = couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).all();
assertEquals(2, result.size(), "should have deleted user1 and user2");
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user2.getId()));
}
@Test
void removeByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user2.getId()));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(specialUser.getId()));
Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special"));
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName)
.matching(nonSpecialUsers).all();
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user2.getId()));
assertNotNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(specialUser.getId()));
}
@Test
void distinct() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
couchbaseTemplate.insertById(Airport.class).inCollection(collectionName).one(airport);
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(Airport.class)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
assertEquals(7, airports2.size());
// count( distinct { iata, icao } )
long count1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "iata", "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).count();
assertEquals(7, count1);
// count( distinct (all fields in icaoClass)
Class icaoClass = (new Object() {
String iata;
String icao;
}).getClass();
long count2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).count();
assertEquals(7, count2);
} finally {
couchbaseTemplate.removeById().inCollection(collectionName)
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}
@Test
void distinctReactive() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
reactiveCouchbaseTemplate.insertById(Airport.class).inCollection(collectionName).one(airport).block();
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all().collectList().block();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {})
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all().collectList().block();
assertEquals(7, airports2.size());
// count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(2, count1);
// count( distinct (all fields in icaoClass) // which only has one field
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
Class icaoClass = (new Object() {
String icao;
}).getClass();
long count2 = (long) reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(7, count2);
} finally {
reactiveCouchbaseTemplate.removeById().inCollection(collectionName)
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet())).collectList()
.block();
}
}
/**
* find . -name 'Exec*OperationSupport.java'|awk -F/ '{print $NF}'|sort| awk -F. '{print "* ", NR, ")",$1, ""}'<br>
* 1) ExecutableExistsByIdOperationSupport <br>
* 2) ExecutableFindByAnalyticsOperationSupport <br>
* 3) ExecutableFindByIdOperationSupport <br>
* 4) ExecutableFindByQueryOperationSupport <br>
* 5) ExecutableFindFromReplicasByIdOperationSupport <br>
* 6) ExecutableInsertByIdOperationSupport <br>
* 7) ExecutableRemoveByIdOperationSupport <br>
* 8) ExecutableRemoveByQueryOperationSupport <br>
* 9) ExecutableReplaceByIdOperationSupport <br>
* 10)ExecutableUpsertByIdOperationSupport <br>
*/
@Test
public void existsById() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("398"));
try {
Boolean exists = couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName)
.withOptions(existsOptions).one(saved.getId());
assertTrue(exists, "Airport should exist: " + saved.getId());
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalytics() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("413"));
try {
List<Airport> found = couchbaseTemplate.findByAnalytics(Airport.class).inScope(scopeName)
.inCollection(collectionName).withOptions(options).all();
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findById() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("427"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findByQuery() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("441"));
try {
List<Airport> found = couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(scopeName).inCollection(collectionName).withOptions(options).all();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findFromReplicasById() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("456"));
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(scopeName)
.inCollection(collectionName).withOptions(options).any(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void insertById() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withId(UUID.randomUUID().toString()));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void removeById() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("485"));
RemoveResult removeResult = couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId());
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQuery() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("495"));
List<RemoveResult> removeResults = couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(scopeName).inCollection(collectionName).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceById() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(insertOptions).one(vie.withIcao("508"));
Airport replaced = couchbaseTemplate.replaceById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("newIcao"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(replaced, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void upsertById() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.upsertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("526"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void existsByIdOther() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowg"));
try {
Boolean exists = couchbaseTemplate.existsById().inScope(otherScope).inCollection(otherCollection)
.withOptions(existsOptions).one(vie.getId());
assertTrue(exists, "Airport should exist: " + vie.getId());
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(vie.getId());
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOther() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("566"));
try {
List<Airport> found = couchbaseTemplate.findByAnalytics(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).all();
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findByIdOther() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("580"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findByQueryOther() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("594"));
try {
List<Airport> found = couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(otherScope).inCollection(otherCollection).withOptions(options).all();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findFromReplicasByIdOther() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("609"));
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).any(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void insertByIdOther() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withId(UUID.randomUUID().toString()));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void removeByIdOther() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("638"));
RemoveResult removeResult = couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId());
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQueryOther() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("648"));
List<RemoveResult> removeResults = couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(otherScope).inCollection(otherCollection).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceByIdOther() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(insertOptions).one(vie.withIcao("661"));
Airport replaced = couchbaseTemplate.replaceById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("newIcao"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(replaced, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void upsertByIdOther() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.upsertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("679"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void existsByIdOptions() { // 1 - Options
ExistsOptions options = ExistsOptions.existsOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> couchbaseTemplate.existsById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOptions() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.findByAnalytics(Airport.class)
.inScope(otherScope).inCollection(otherCollection).withOptions(options).all());
}
@Test
public void findByIdOptions() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> couchbaseTemplate.findById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
public void findByQueryOptions() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(REQUEST_PLUS).inScope(otherScope).inCollection(otherCollection).withOptions(options).all());
}
@Test
public void findFromReplicasByIdOptions() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofNanos(1000));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("723"));
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).any(saved.getId());
assertNull(found, "should not have found document in short timeout");
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void insertByIdOptions() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.insertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withId(UUID.randomUUID().toString())));
}
@Test
public void removeByIdOptions() { // 7 - options
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("743"));
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.removeById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
public void removeByQueryOptions() { // 8 - options
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class,
() -> couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all());
}
@Test
public void replaceByIdOptions() { // 9 - options
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.replaceById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withIcao("newIcao")));
}
@Test
public void upsertByIdOptions() { // 10 - options
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.upsertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withIcao("770")));
}
@Test
public void testScopeCollectionAnnotation() {
UserCol user = new UserCol("1", "Dave", "Wilson");
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
try {
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
.one(user);
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class).withConsistency(REQUEST_PLUS)
.inScope(scopeName).inCollection(collectionName).matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} finally {
try {
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
try {
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
.one(user);
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class).withConsistency(REQUEST_PLUS)
.inScope(scopeName).inCollection(collectionName).matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} finally {
try {
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
void testFluentApi() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
DurabilityLevel dl = DurabilityLevel.NONE;
User result;
RemoveResult rr;
result = couchbaseTemplate.insertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
.one(user1);
assertEquals(user1, result);
result = couchbaseTemplate.upsertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
.one(user1);
assertEquals(user1, result);
result = couchbaseTemplate.replaceById(User.class).withDurability(dl).inScope(scopeName)
.inCollection(collectionName).one(user1);
assertEquals(user1, result);
rr = couchbaseTemplate.removeById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
.one(user1.getId());
assertEquals(rr.getId(), user1.getId());
assertEquals(user1, result);
result = reactiveCouchbaseTemplate.insertById(User.class).withDurability(dl).inScope(scopeName)
.inCollection(collectionName).one(user1).block();
assertEquals(user1, result);
result = reactiveCouchbaseTemplate.upsertById(User.class).withDurability(dl).inScope(scopeName)
.inCollection(collectionName).one(user1).block();
assertEquals(user1, result);
result = reactiveCouchbaseTemplate.replaceById(User.class).withDurability(dl).inScope(scopeName)
.inCollection(collectionName).one(user1).block();
assertEquals(user1, result);
rr = reactiveCouchbaseTemplate.removeById(User.class).withDurability(dl).inScope(scopeName)
.inCollection(collectionName).one(user1.getId()).block();
assertEquals(rr.getId(), user1.getId());
}
}

View File

@@ -0,0 +1,369 @@
/*
* Copyright 2012-2022 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.core;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
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.AssessmentDO;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserJustLastName;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.domain.UserSubmissionProjected;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* Query tests Theses tests rely on a cb server running
*
* @author Michael Nitschinger
* @author Michael Reiche
* @author Haris Alesevic
* @author Mauro Monti
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(Config.class)
class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeEach
@Override
public void beforeEach() {
super.beforeEach();
// already setup by JavaIntegrationTests.beforeAll()
// ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
// reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE);
// ensure each test starts with clean state
couchbaseTemplate.removeByQuery(User.class).all();
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).all();
}
@Test
void findByQueryAll() {
try {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).all();
for (User u : foundUsers) {
if (!(u.equals(user1) || u.equals(user2))) {
// somebody didn't clean up after themselves.
couchbaseTemplate.removeById().one(u.getId());
}
}
assertEquals(2, foundUsers.size());
TemporalAccessor auditTime = new AuditingDateTimeProvider().getNow().get();
long auditMillis = Instant.from(auditTime).toEpochMilli();
String auditUser = new NaiveAuditorAware().getCurrentAuditor().get();
for (User u : foundUsers) {
assertTrue(u.equals(user1) || u.equals(user2));
assertEquals(auditUser, u.getCreatedBy());
assertEquals(auditMillis, u.getCreatedDate());
assertEquals(auditUser, u.getLastModifiedBy());
assertEquals(auditMillis, u.getLastModifiedDate());
}
couchbaseTemplate.findById(User.class).one(user1.getId());
reactiveCouchbaseTemplate.findById(User.class).one(user1.getId()).block();
} finally {
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).all();
}
User usery = couchbaseTemplate.findById(User.class).one("user1");
assertNull(usery, "user1 should have been deleted");
User userz = reactiveCouchbaseTemplate.findById(User.class).one("user2").block();
assertNull(userz, "user2 should have been deleted");
}
@Test
void findByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where(i("firstname")).like("special"));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.matching(specialUsers).all();
assertEquals(1, foundUsers.size());
Query arrayContaining = new Query(QueryCriteria.where(i("firstname")).arrayContaining("not_match_anything"));
final List<User> foundArrayContaining = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.matching(arrayContaining).all();
assertEquals(0, foundArrayContaining.size());
}
@Test
void findAssessmentDO() {
AssessmentDO ado = new AssessmentDO();
ado.setEventTimestamp(44444444);// this is also an @IdAttribute
ado.setId("123");
ado = couchbaseTemplate.upsertById(AssessmentDO.class).one(ado);
Query specialUsers = new Query(QueryCriteria.where(i("id")).is(ado.getId()));
final List<AssessmentDO> foundUsers = couchbaseTemplate.findByQuery(AssessmentDO.class)
.withConsistency(REQUEST_PLUS).matching(specialUsers).all();
assertEquals("123", foundUsers.get(0).getId(), "id");
assertEquals("44444444", foundUsers.get(0).getDocumentId(), "documentId");
assertEquals(ado, foundUsers.get(0));
couchbaseTemplate.removeById(AssessmentDO.class).one(ado.getDocumentId());
}
@Test
void findByMatchingQueryProjected() {
couchbaseTemplate.removeByQuery(UserSubmission.class).all();
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user.setRoles(Arrays.asList("role1", "role2"));
Address address = new Address();
address.setStreet("1234 Olcott Street");
user.setAddress(address);
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
user.setCourses(Arrays.asList(new Course(UUID.randomUUID().toString(), user.getId(), "581"),
new Course(UUID.randomUUID().toString(), user.getId(), "777")));
couchbaseTemplate.upsertById(UserSubmission.class).one(user);
Query daveUsers = new Query(QueryCriteria.where("username").like("dave"));
final List<UserSubmissionProjected> foundUserSubmissions = couchbaseTemplate.findByQuery(UserSubmission.class)
.as(UserSubmissionProjected.class).withConsistency(REQUEST_PLUS).matching(daveUsers).all();
assertEquals(1, foundUserSubmissions.size());
assertEquals(user.getUsername(), foundUserSubmissions.get(0).getUsername());
assertEquals(user.getId(), foundUserSubmissions.get(0).getId());
assertEquals(user.getCourses(), foundUserSubmissions.get(0).getCourses());
assertEquals(user.getAddress(), foundUserSubmissions.get(0).getAddress());
couchbaseTemplate.removeByQuery(UserSubmission.class).all();
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
final List<UserJustLastName> foundUsers = couchbaseTemplate.findByQuery(User.class).as(UserJustLastName.class)
.withConsistency(REQUEST_PLUS).matching(specialUsers).all();
assertEquals(1, foundUsers.size());
final List<UserJustLastName> foundUsersReactive = reactiveCouchbaseTemplate.findByQuery(User.class)
.as(UserJustLastName.class).withConsistency(REQUEST_PLUS).matching(specialUsers).all().collectList().block();
assertEquals(1, foundUsersReactive.size());
couchbaseTemplate.removeById(User.class).all(Arrays.asList(user1.getId(), user2.getId(), specialUser.getId()));
}
@Test
void removeByQueryAll() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2));
assertTrue(couchbaseTemplate.existsById().one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().one(user2.getId()));
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).all();
assertNull(couchbaseTemplate.findById(User.class).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).one(user2.getId()));
}
@Test
void removeByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2, specialUser));
assertTrue(couchbaseTemplate.existsById().one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().one(user2.getId()));
assertTrue(couchbaseTemplate.existsById().one(specialUser.getId()));
Query nonSpecialUsers = new Query(QueryCriteria.where(i("firstname")).notLike("special"));
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).matching(nonSpecialUsers).all();
assertNull(couchbaseTemplate.findById(User.class).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).one(user2.getId()));
assertNotNull(couchbaseTemplate.findById(User.class).one(specialUser.getId()));
}
@Test
void distinct() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
couchbaseTemplate.insertById(Airport.class).one(airport);
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).all();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(Airport.class)
.withConsistency(REQUEST_PLUS).all();
assertEquals(7, airports2.size());
// count( distinct { iata, icao } )
long count1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "iata", "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).count();
assertEquals(7, count1);
// count( distinct (all fields in icaoClass)
Class icaoClass = (new Object() {
String iata;
String icao;
}).getClass();
long count2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(REQUEST_PLUS).count();
assertEquals(7, count2);
} finally {
couchbaseTemplate.removeById()
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}
@Test
void distinctReactive() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
reactiveCouchbaseTemplate.insertById(Airport.class).one(airport).block();
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).all().collectList().block();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {})
.as(Airport.class).withConsistency(REQUEST_PLUS).all().collectList().block();
assertEquals(7, airports2.size());
// count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).count().block();
assertEquals(2, count1);
// count( distinct { icao, iata } )
Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao", "iata" })
.withConsistency(REQUEST_PLUS).count().block();
assertEquals(7, count2);
} finally {
reactiveCouchbaseTemplate.removeById()
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet())).collectList()
.block();
}
}
@Test
void sortedTemplate() {
couchbaseTemplate.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS).all();
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
try {
couchbaseTemplate.insertById(Airport.class)
.all(Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
org.springframework.data.couchbase.core.query.Query query = org.springframework.data.couchbase.core.query.Query
.query(QueryCriteria.where("iata").isNotNull());
Pageable pageableWithSort = PageRequest.of(0, 7, Sort.by("iata"));
query.with(pageableWithSort);
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.matching(query).all();
String[] sortedIatas = iatas.clone();
System.out.println("" + iatas.length + " " + sortedIatas.length);
Arrays.sort(sortedIatas);
for (int i = 0; i < pageableWithSort.getPageSize(); i++) {
System.out.println(airports.get(i).getIata());
assertEquals(sortedIatas[i], airports.get(i).getIata());
}
} finally {
couchbaseTemplate.removeById(Airport.class)
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2012-2022 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.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.time.Duration;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMapper;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.kv.GetResult;
/**
* @author Michael Reiche
*/
@SpringJUnitConfig(CustomTypeKeyIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class CustomTypeKeyIntegrationTests extends ClusterAwareIntegrationTests {
private static final String CUSTOM_TYPE_KEY = "javaClass";
@Autowired private CouchbaseOperations operations;
@Autowired private CouchbaseClientFactory clientFactory;
@Test
void saveSimpleEntityCorrectlyWithDifferentTypeKey() {
clientFactory.getBucket().waitUntilReady(Duration.ofSeconds(10));
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
// When using 'mocked', this call runs fine when the test class is ran by itself,
// but it times-out when ran together with all the tests under
// org.springframework.data.couchbase
User modified = operations.upsertById(User.class).one(user);
assertEquals(user, modified);
GetResult getResult = clientFactory.getCollection(null).get(user.getId());
assertEquals("abstractuser", getResult.contentAsObject().getString(CUSTOM_TYPE_KEY));
assertFalse(getResult.contentAsObject().containsKey(DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY));
operations.removeById(User.class).one(user.getId());
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Override
public String typeKey() {
return CUSTOM_TYPE_KEY;
}
}
}

View File

@@ -0,0 +1,389 @@
/*
* Copyright 2012-2022 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.core;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
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 java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.core.ReactiveFindByIdOperation.ReactiveFindById;
import org.springframework.data.couchbase.core.ReactiveRemoveByIdOperation.ReactiveRemoveById;
import org.springframework.data.couchbase.core.ReactiveReplaceByIdOperation.ReactiveReplaceById;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
import org.springframework.data.couchbase.core.support.WithDurability;
import org.springframework.data.couchbase.core.support.WithExpiry;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.PersonValue;
import org.springframework.data.couchbase.domain.ReactiveNaiveAuditorAware;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserAnnotated;
import org.springframework.data.couchbase.domain.UserAnnotated2;
import org.springframework.data.couchbase.domain.UserAnnotated3;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* KV tests Theses tests rely on a cb server running.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(Config.class)
class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeEach
@Override
public void beforeEach() {
super.beforeEach();
List<RemoveResult> r1 = reactiveCouchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).all()
.collectList().block();
List<RemoveResult> r2 = reactiveCouchbaseTemplate.removeByQuery(UserAnnotated.class).withConsistency(REQUEST_PLUS)
.all().collectList().block();
List<RemoveResult> r3 = reactiveCouchbaseTemplate.removeByQuery(UserAnnotated2.class).withConsistency(REQUEST_PLUS)
.all().collectList().block();
List<UserAnnotated2> f3 = reactiveCouchbaseTemplate.findByQuery(UserAnnotated2.class).withConsistency(REQUEST_PLUS)
.all().collectList().block();
}
@Test
void findByIdWithExpiry() {
try {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
Collection<User> upserts = (Collection<User>) reactiveCouchbaseTemplate.upsertById(User.class)
.all(Arrays.asList(user1, user2)).collectList().block();
User foundUser = reactiveCouchbaseTemplate.findById(User.class).withExpiry(Duration.ofSeconds(1))
.one(user1.getId()).block();
user1.setVersion(foundUser.getVersion());// version will have changed
assertEquals(user1, foundUser);
int tries = 0;
Collection<User> foundUsers;
do {
sleepSecs(1);
foundUsers = (Collection<User>) reactiveCouchbaseTemplate.findById(User.class)
.all(Arrays.asList(user1.getId(), user2.getId())).collectList().block();
} while (tries++ < 10 && foundUsers.size() != 1 && !user2.equals(foundUsers.iterator().next()));
assertEquals(1, foundUsers.size(), "should have found exactly 1 user");
assertEquals(user2, foundUsers.iterator().next());
} finally {
reactiveCouchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).all().collectList().block();
}
}
@Test
void upsertAndFindById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
// create a new object so that the object returned by replaceById() is a different object from the original user
// don't need to copy the ModifiedDate/ModifiedTime as they are not read and are overwritten.
User modifying = new User(user.getId(), user.getFirstname(), user.getLastname());
modifying.setCreatedDate(user.getCreatedDate());
modifying.setCreatedBy(user.getCreatedBy());
modifying.setVersion(user.getVersion());
modified = reactiveCouchbaseTemplate.replaceById(User.class).one(modifying).block();
assertEquals(modifying, modified);
if (user == modified) {
throw new RuntimeException(" user == modified ");
}
assertNotEquals(user, modified);
assertEquals(ReactiveNaiveAuditorAware.AUDITOR, modified.getCreatedBy());
assertEquals(ReactiveNaiveAuditorAware.AUDITOR, modified.getLastModifiedBy());
assertNotEquals(0, modified.getCreatedDate());
assertNotEquals(0, modified.getLastModifiedDate());
// The FixedDateTimeService of the AuditingDateTimeProvider will guarantee these are equal
assertEquals(user.getLastModifiedDate(), modified.getLastModifiedDate());
User badUser = new User(user.getId(), user.getFirstname(), user.getLastname());
badUser.setVersion(12345678);
assertThrows(OptimisticLockingFailureException.class,
() -> reactiveCouchbaseTemplate.replaceById(User.class).one(badUser).block());
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
user.setVersion(found.getVersion());
assertEquals(modified, found);
reactiveCouchbaseTemplate.removeById().one(user.getId()).block();
}
@Test
void withDurability()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
// insert, replace, upsert
for (OneAndAllEntityReactive<User> operator : new OneAndAllEntityReactive[] {
reactiveCouchbaseTemplate.insertById(clazz), reactiveCouchbaseTemplate.replaceById(clazz),
reactiveCouchbaseTemplate.upsertById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
operator = (OneAndAllEntityReactive<User>) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
ReplicateTo.NONE);
}
// if replace, we need to insert a document to replace
if (operator instanceof ReactiveReplaceById) {
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update
User returned = operator.one(user).block();
assertEquals(user, returned);
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
assertEquals(user, found);
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById) {
reactiveCouchbaseTemplate.removeById().withDurability(PersistTo.ACTIVE, ReplicateTo.NONE).one(user.getId())
.block();
User removed = (User) reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
assertNull(removed, "found should have been null as document should be removed");
}
}
}
@Test
void withExpiryAndExpiryAnnotation()
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// ( withExpiry()<User>, expiry=1<UserAnnotated>, expiryExpression=${myExpiry}<UserAnnotated2> ) X ( insert,
// replace, upsert )
Set<User> users = new HashSet<>(); // set of all documents we will insert
// Entity classes
for (Class<?> clazz : new Class[] { User.class, UserAnnotated.class, UserAnnotated2.class, UserAnnotated3.class }) {
// insert, replace, upsert
for (Object operator : new Object[] { reactiveCouchbaseTemplate.insertById(clazz),
reactiveCouchbaseTemplate.replaceById(clazz), reactiveCouchbaseTemplate.upsertById(clazz),
reactiveCouchbaseTemplate.findById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an expiry annotation
operator = ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(1));
} else if (clazz.equals(UserAnnotated3.class)) { // override the expiry from the annotation with no expiry
operator = ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(0));
}
// if replace, remove or find, we need to insert a document first
if (operator instanceof ReactiveReplaceById || operator instanceof ReactiveRemoveById
|| operator instanceof ReactiveFindById) {
user = reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update/find
User returned = operator instanceof OneAndAllEntityReactive
? ((OneAndAllEntityReactive<User>) operator).one(user).block()
: ((OneAndAllIdReactive<User>) operator).one(user.getId()).block();
if (operator instanceof OneAndAllIdReactive) { // the user.version won't be updated
user.setVersion(returned.getVersion());
}
assertEquals(user, returned);
users.add(user);
}
}
// check that they are gone after a few seconds.
int tries = 0;
List<String> errorList = new LinkedList<>();
do {
sleepSecs(1);
for (User user : users) {
errorList = new LinkedList<>();
User found = reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
if (user.getId().endsWith(UserAnnotated3.class.getSimpleName())) {
if (found == null) {
errorList.add("\nfound should be non null as it was set to have no expiry " + user.getId());
}
} else {
if (found != null) {
errorList.add("\nfound should have been null as document should be expired " + user.getId());
}
}
if (found != null) {
couchbaseTemplate.removeById(user.getClass()).one(user.getId());
}
}
} while (tries++ < 10 && !errorList.isEmpty());
if (!errorList.isEmpty()) {
throw new RuntimeException(errorList.toString());
}
}
@Test
void findDocWhichDoesNotExist() {
assertNull(reactiveCouchbaseTemplate.findById(User.class).one(UUID.randomUUID().toString()).block());
}
@Test
void upsertAndReplaceById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
User toReplace = new User(modified.getId(), "some other", "lastname");
reactiveCouchbaseTemplate.replaceById(User.class).one(toReplace).block();
User loaded = reactiveCouchbaseTemplate.findById(User.class).one(toReplace.getId()).block();
assertEquals("some other", loaded.getFirstname());
reactiveCouchbaseTemplate.removeById().one(toReplace.getId()).block();
}
@Test
void upsertAndRemoveById() {
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
RemoveResult removeResult = reactiveCouchbaseTemplate.removeById().one(user.getId()).block();
assertEquals(user.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
assertNull(reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block());
}
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
// careful now - user and modified are the same object. The object has the new cas (@Version version)
Long savedCas = modified.getVersion();
modified.setVersion(123);
assertThrows(OptimisticLockingFailureException.class, () -> reactiveCouchbaseTemplate.removeById()
.withCas(reactiveCouchbaseTemplate.support().getCas(modified)).one(modified.getId()).block());
modified.setVersion(savedCas);
reactiveCouchbaseTemplate.removeById().withCas(reactiveCouchbaseTemplate.support().getCas(modified))
.one(modified.getId()).block();
}
}
@Test
void insertById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
}
@Test
void insertByIdwithDurability() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.NONE)
.one(user).block();
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
}
@Test
void existsById() {
String id = UUID.randomUUID().toString();
assertFalse(reactiveCouchbaseTemplate.existsById().one(id).block());
User user = new User(id, "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
assertEquals(user, inserted);
assertTrue(reactiveCouchbaseTemplate.existsById().one(id).block());
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindImmutableById() {
PersonValue personValue = new PersonValue(UUID.randomUUID().toString(), 123, "329", "l");
PersonValue inserted;
PersonValue upserted;
PersonValue replaced;
inserted = reactiveCouchbaseTemplate.insertById(PersonValue.class).one(personValue).block();
assertNotEquals(0, inserted.getVersion());
PersonValue foundInserted = reactiveCouchbaseTemplate.findById(PersonValue.class).one(inserted.getId()).block();
assertNotNull(foundInserted, "inserted personValue not found");
assertEquals(inserted, foundInserted);
// upsert will insert
reactiveCouchbaseTemplate.removeById().one(inserted.getId());
upserted = reactiveCouchbaseTemplate.upsertById(PersonValue.class).one(inserted).block();
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted = reactiveCouchbaseTemplate.findById(PersonValue.class).one(upserted.getId()).block();
assertNotNull(foundUpserted, "upserted personValue not found");
assertEquals(upserted, foundUpserted);
// upsert will replace
upserted = reactiveCouchbaseTemplate.upsertById(PersonValue.class).one(inserted).block();
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted2 = reactiveCouchbaseTemplate.findById(PersonValue.class).one(upserted.getId()).block();
assertNotNull(foundUpserted2, "upserted personValue not found");
assertEquals(upserted, foundUpserted2);
replaced = reactiveCouchbaseTemplate.replaceById(PersonValue.class).one(upserted).block();
assertNotEquals(0, replaced.getVersion());
PersonValue foundReplaced = reactiveCouchbaseTemplate.findById(PersonValue.class).one(replaced.getId()).block();
assertNotNull(foundReplaced, "replaced personValue not found");
assertEquals(replaced, foundReplaced);
couchbaseTemplate.removeById(PersonValue.class).one(replaced.getId());
}
private void sleepSecs(int i) {
try {
Thread.sleep(i * 1000);
} catch (InterruptedException ie) {}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-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.core.convert.translation;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
/**
* Verifies the functionality of a {@link JacksonTranslationService}.
*
* @author Michael Nitschinger
*/
public class JacksonTranslationServiceTests {
private static TranslationService service;
@BeforeAll
static void beforeAll() {
service = new JacksonTranslationService();
((JacksonTranslationService) service).afterPropertiesSet();
}
@Test
void shouldEncodeNonASCII() {
CouchbaseDocument doc = new CouchbaseDocument("key");
doc.put("language", "русский");
String expected = "{\"language\":\"русский\"}";
assertEquals(expected, service.encode(doc));
}
@Test
void shouldDecodeNonASCII() {
String source = "{\"language\":\"русский\"}";
CouchbaseDocument target = new CouchbaseDocument();
service.decode(source, target);
assertEquals("русский", target.get("language"));
}
@Test
void shouldDecodeAdHocFragment() {
String source = "{\"language\":\"french\"}";
LanguageFragment f = service.decodeFragment(source, LanguageFragment.class);
assertNotNull(f);
assertEquals("french", f.language);
}
static class LanguageFragment {
public String language;
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright 2013-2022 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.core.mapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.mock.env.MockPropertySource;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig
@TestPropertySource(properties = { "valid.document.expiry = 10", "invalid.document.expiry = abc" })
public class BasicCouchbasePersistentEntityTests {
@Autowired ConfigurableEnvironment environment;
@Test
void testNoExpiryByDefault() {
CouchbasePersistentEntity<DefaultExpiry> entity = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(DefaultExpiry.class));
assertThat(entity.getExpiryDuration().getSeconds()).isEqualTo(0);
}
@Test
void testDefaultExpiryUnitIsSeconds() {
CouchbasePersistentEntity<DefaultExpiryUnit> entity = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(DefaultExpiryUnit.class));
assertThat(entity.getExpiryDuration().getSeconds()).isEqualTo(78);
}
@Test
void testLargeExpiry30DaysStillInSeconds() {
CouchbasePersistentEntity<LimitDaysExpiry> entityUnder = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(LimitDaysExpiry.class));
assertThat(entityUnder.getExpiryDuration().getSeconds()).isEqualTo(30 * 24 * 60 * 60);
}
@Test
void testLargeExpiry31DaysIsConvertedToUnixUtcTime() {
CouchbasePersistentEntity<OverLimitDaysExpiry> entityOver = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(OverLimitDaysExpiry.class));
int expiryOver = (int) entityOver.getExpiry();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.clear();
calendar.add(Calendar.SECOND, expiryOver);
assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR));
assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH));
assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH));
assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY));
assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE));
assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND));
}
@Test
void testLargeExpiryExpression31DaysIsConvertedToUnixUtcTime() {
BasicCouchbasePersistentEntity<OverLimitDaysExpiryExpression> entityOver = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(OverLimitDaysExpiryExpression.class));
entityOver.setEnvironment(environment);
int expiryOver = (int) entityOver.getExpiry();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.clear();
calendar.add(Calendar.SECOND, expiryOver);
assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR));
assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH));
assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH));
assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY));
assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE));
assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND));
}
@Test
void testLargeExpiry31DaysInSecondsIsConvertedToUnixUtcTime() {
CouchbasePersistentEntity<OverLimitSecondsExpiry> entityOver = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(OverLimitSecondsExpiry.class));
int expiryOver = (int) entityOver.getExpiry();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.clear();
calendar.add(Calendar.SECOND, expiryOver);
assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR));
assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH));
assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH));
assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY));
assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE));
assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND));
}
@Test
void doesNotUseGetExpiry() {
assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).getExpiry()).isEqualTo(0);
}
@Test
void usesGetExpiry() {
assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).getExpiry()).isEqualTo(10);
}
@Test
void doesNotUseIsUpdateExpiryForRead() {
assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).isTouchOnRead()).isFalse();
assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).isTouchOnRead()).isFalse();
}
@Test
void usesTouchOnRead() {
assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithTouchOnRead.class).isTouchOnRead()).isTrue();
}
@Test
void usesGetExpiryExpression() {
assertThat(getBasicCouchbasePersistentEntity(ConstantExpiryExpression.class).getExpiry()).isEqualTo(10);
}
@Test
void usesGetExpiryFromValidExpression() {
assertThat(getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class).getExpiry()).isEqualTo(10);
}
@Test
void doesNotAllowUseExpiryFromInvalidExpression() {
assertThrows(IllegalArgumentException.class,
() -> getBasicCouchbasePersistentEntity(ExpiryWithInvalidExpression.class).getExpiry());
}
@Test
void usesGetExpiryExpressionAndRespectsPropertyUpdates() {
BasicCouchbasePersistentEntity entity = getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class);
assertThat(entity.getExpiry()).isEqualTo(10);
environment.getPropertySources().addFirst(new MockPropertySource().withProperty("valid.document.expiry", "20"));
assertThat(entity.getExpiry()).isEqualTo(20);
}
@Test
void failsIfExpiryExpressionMissesRequiredProperty() {
assertThrows(IllegalArgumentException.class,
() -> getBasicCouchbasePersistentEntity(ExpiryWithMissingProperty.class).getExpiry());
}
@Test
void doesNotAllowUseExpiryAndExpressionSimultaneously() {
assertThrows(IllegalArgumentException.class,
() -> getBasicCouchbasePersistentEntity(ExpiryAndExpression.class).getExpiry());
}
private BasicCouchbasePersistentEntity getBasicCouchbasePersistentEntity(Class<?> clazz) {
BasicCouchbasePersistentEntity basicCouchbasePersistentEntity = new BasicCouchbasePersistentEntity(
ClassTypeInformation.from(clazz));
basicCouchbasePersistentEntity.setEnvironment(environment);
return basicCouchbasePersistentEntity;
}
@Configuration
static class Config {}
class SimpleDocument {}
@Document(expiry = 10)
class SimpleDocumentWithExpiry {}
@Document(expiry = 10, touchOnRead = true)
class SimpleDocumentWithTouchOnRead {}
/**
* Simple POJO to test default expiry.
*/
@Document
class DefaultExpiry {}
/**
* Simple POJO to test default expiry unit.
*/
@Document(expiry = 78)
class DefaultExpiryUnit {}
/**
* Simple POJO to test limit expiry.
*/
@Document(expiry = 30, expiryUnit = TimeUnit.DAYS)
class LimitDaysExpiry {}
/**
* Simple POJO to test larger than 30 days expiry.
*/
@Document(expiry = 31, expiryUnit = TimeUnit.DAYS)
class OverLimitDaysExpiry {}
/**
* Simple POJO to test larger than 30 days expiry defined as an expression
*/
@Document(expiryExpression = "${document.expiry.larger.than.30days:31}", expiryUnit = TimeUnit.DAYS)
class OverLimitDaysExpiryExpression {}
/**
* Simple POJO to test larger than 30 days expiry, when expressed in default time unit (SECONDS).
*/
@Document(expiry = 31 * 24 * 60 * 60)
class OverLimitSecondsExpiry {}
/**
* Simple POJO to test constant expiry expression
*/
@Document(expiryExpression = "10")
class ConstantExpiryExpression {}
/**
* Simple POJO to test valid expiry expression by resolving simple property from environment
*/
@Document(expiryExpression = "${valid.document.expiry}")
class ExpiryWithValidExpression {}
/**
* Simple POJO to test invalid expiry expression
*/
@Document(expiryExpression = "${invalid.document.expiry}")
class ExpiryWithInvalidExpression {}
/**
* Simple POJO to test expiry expression logic failure to resolve property placeholder
*/
@Document(expiryExpression = "${missing.expiry}")
class ExpiryWithMissingProperty {}
/**
* Simple POJO to test that expiry and expiry expression cannot be used simultaneously
*/
@Document(expiry = 10, expiryExpression = "10")
class ExpiryAndExpression {}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.core.mapping;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.ReflectionUtils;
/**
* Verifies the correct behavior of properties on persistable objects.
*
* @author Michael Nitschinger
* @author Mark Paluch
*/
public class BasicCouchbasePersistentPropertyTests {
/**
* Holds the entity to test against (contains the properties).
*/
CouchbasePersistentEntity<Beer> entity;
/**
* Create an instance of the demo entity.
*/
@BeforeEach
void beforeEach() {
entity = new BasicCouchbasePersistentEntity<>(ClassTypeInformation.from(Beer.class));
}
/**
* Verifies the name of the property without annotations.
*/
@Test
void usesPropertyFieldName() {
Field field = ReflectionUtils.findField(Beer.class, "description");
assertThat(getPropertyFor(field).getFieldName()).isEqualTo("description");
}
/**
* Verifies the name of the property with custom name annotation.
*/
@Test
void usesAnnotatedFieldName() {
Field field = ReflectionUtils.findField(Beer.class, "name");
assertThat(getPropertyFor(field).getFieldName()).isEqualTo("name");
}
@Test
void testSdkIdAnnotationEvaluatedAfterSpringIdAnnotationIsIgnored() {
BasicCouchbasePersistentEntity<Beer> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(Beer.class));
Field springIdField = ReflectionUtils.findField(Beer.class, "springId");
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
// here this simulates the order in which the annotations would be found
// when "overriding" Spring @Id with SDK's @Id...
test.addPersistentProperty(springIdProperty);
assertThat(test.getIdProperty()).isEqualTo(springIdProperty);
}
@Test
void testAnnotationIdFieldOnly() { // only has @springId
class TestIdField {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
@Id private String springId;
}
BasicCouchbasePersistentEntity<TestIdField> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(TestIdField.class));
Field springIdField = ReflectionUtils.findField(TestIdField.class, "springId");
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
test.addPersistentProperty(springIdProperty);
assertThat(test.getIdProperty()).isEqualTo(springIdProperty);
}
@Test
void testIdFieldOnly() { // only has id
class TestIdField {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
private String id;
}
Field idField = ReflectionUtils.findField(TestIdField.class, "id");
CouchbasePersistentProperty idProperty = getPropertyFor(idField);
BasicCouchbasePersistentEntity<TestIdField> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(TestIdField.class));
test.addPersistentProperty(idProperty);
assertThat(test.getIdProperty()).isEqualTo(idProperty);
}
@Test
void testIdFieldAndAnnotationIdField() { // has @springId and id
class TestIdField {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
@Id private String springId;
private String id;
}
BasicCouchbasePersistentEntity<TestIdField> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(TestIdField.class));
Field springIdField = ReflectionUtils.findField(TestIdField.class, "springId");
Field idField = ReflectionUtils.findField(TestIdField.class, "id");
CouchbasePersistentProperty idProperty = getPropertyFor(idField);
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
// here this simulates the order in which the annotations would be found
// when "overriding" Spring @Id with SDK's @Id...
test.addPersistentProperty(idProperty);
// replace id with springId
test.addPersistentProperty(springIdProperty);
assertThat(test.getIdProperty()).isEqualTo(springIdProperty);
}
@Test
void testTwoAnnotationIdFields() { // has @Id springId and @Id id
class TestIdField {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
@Id private String springId;
@Id private String id;
}
Field springIdField = ReflectionUtils.findField(TestIdField.class, "springId");
Field idField = ReflectionUtils.findField(TestIdField.class, "id");
CouchbasePersistentProperty idProperty = getPropertyFor(idField);
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
BasicCouchbasePersistentEntity<TestIdField> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(TestIdField.class));
test.addPersistentProperty(springIdProperty);
assertThatExceptionOfType(MappingException.class).isThrownBy(() -> {
test.addPersistentProperty(idProperty);
});
}
@Test
void testTwoIdFields() { // has @Field("id") springId and id
class TestIdField {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
@org.springframework.data.couchbase.core.mapping.Field("id") private String springId;
private String id;
}
Field springIdField = ReflectionUtils.findField(TestIdField.class, "springId");
Field idField = ReflectionUtils.findField(TestIdField.class, "id");
CouchbasePersistentProperty idProperty = getPropertyFor(idField);
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
BasicCouchbasePersistentEntity<TestIdField> test = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(TestIdField.class));
test.addPersistentProperty(springIdProperty);
assertThatExceptionOfType(MappingException.class).isThrownBy(() -> {
test.addPersistentProperty(idProperty);
});
}
/**
* Helper method to create a property out of the field.
*
* @param field the field to retrieve the properties from.
* @return the actual BasicCouchbasePersistentProperty instance.
*/
private CouchbasePersistentProperty getPropertyFor(Field field) {
ClassTypeInformation<?> type = ClassTypeInformation.from(field.getDeclaringClass());
return new BasicCouchbasePersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT,
PropertyNameFieldNamingStrategy.INSTANCE);
}
/**
* Simple POJO to test attribute properties and annotations.
*/
public class Beer {
@org.springframework.data.couchbase.core.mapping.Field String name;
String description;
@Id private String springId;
public String getId() {
return springId;
}
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.core.mapping;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
/**
* Tests to verify custom mapping logic.
*
* @author Michael Nitschinger
* @author Mark Paluch
*/
public class CustomConvertersTests {
private MappingCouchbaseConverter converter;
@BeforeEach
void beforeEach() {
converter = new MappingCouchbaseConverter();
}
@Test
void shouldWriteWithCustomConverter() {
List<Object> converters = new ArrayList<>();
converters.add(DateToStringConverter.INSTANCE);
converter.setCustomConversions(new CouchbaseCustomConversions(converters));
converter.afterPropertiesSet();
Date date = new Date();
BlogPost post = new BlogPost();
post.created = date;
CouchbaseDocument doc = new CouchbaseDocument();
converter.write(post, doc);
assertThat(doc.getContent().get("created")).isEqualTo(date.toString());
}
@Test
void shouldReadWithCustomConverter() {
List<Object> converters = new ArrayList<>();
converters.add(IntegerToStringConverter.INSTANCE);
converter.setCustomConversions(new CouchbaseCustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument doc = new CouchbaseDocument();
doc.getContent().put("content", 10);
Counter loaded = converter.read(Counter.class, doc);
assertThat(loaded.content).isEqualTo("even");
}
@Test
void shouldWriteConvertFullDocument() {
List<Object> converters = new ArrayList<>();
converters.add(BlogPostToCouchbaseDocumentConverter.INSTANCE);
converter.setCustomConversions(new CouchbaseCustomConversions(converters));
converter.afterPropertiesSet();
BlogPost post = new BlogPost();
post.id = "foobar";
post.title = "The Foo of the Bar";
CouchbaseDocument doc = new CouchbaseDocument();
converter.write(post, doc);
assertThat(doc.getContent().get("title")).isEqualTo("The Foo of the Bar");
assertThat(doc.getContent().get("slug")).isEqualTo("the_foo_of_the_bar");
}
@Test
void shouldReadConvertFullDocument() {
List<Object> converters = new ArrayList<>();
converters.add(CouchbaseDocumentToBlogPostConverter.INSTANCE);
converter.setCustomConversions(new CouchbaseCustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument doc = new CouchbaseDocument();
doc.getContent().put("title", "My Title");
BlogPost loaded = converter.read(BlogPost.class, doc);
assertThat(loaded.id).isEqualTo("modified");
assertThat(loaded.title).isEqualTo("My Title!!");
}
public enum IntegerToStringConverter implements Converter<Integer, String> {
INSTANCE;
@Override
public String convert(Integer source) {
return source % 2 == 0 ? "even" : "odd";
}
}
public enum DateToStringConverter implements Converter<Date, String> {
INSTANCE;
@Override
public String convert(Date source) {
return source.toString();
}
}
@WritingConverter
public enum BlogPostToCouchbaseDocumentConverter implements Converter<BlogPost, CouchbaseDocument> {
INSTANCE;
@Override
public CouchbaseDocument convert(BlogPost source) {
return new CouchbaseDocument().setId(source.id).put("title", source.title).put("slug",
source.title.toLowerCase().replaceAll(" ", "_"));
}
}
@ReadingConverter
public enum CouchbaseDocumentToBlogPostConverter implements Converter<CouchbaseDocument, BlogPost> {
INSTANCE;
@Override
public BlogPost convert(CouchbaseDocument source) {
BlogPost post = new BlogPost();
post.id = "modified";
post.title = source.getContent().get("title") + "!!";
return post;
}
}
public static class BlogPost {
@Id public String id = "key";
@Field public Date created;
@Field public String title;
}
public static class Counter {
@Field public String content;
}
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright 2017-2022 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.core.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.meta;
import static org.springframework.data.couchbase.core.query.N1QLExpression.path;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.escapedBucket;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import com.couchbase.client.java.json.JsonArray;
/**
* @author Mauro Monti
* @author Michael Reiche
*/
class QueryCriteriaTests {
@Test
void testSimpleCriteria() {
QueryCriteria c = where(i("name")).is("Bubba");
assertEquals("`name` = \"Bubba\"", c.export());
}
@Test
public void testNullValue() {
QueryCriteria c = where(i("name")).is(null);
assertEquals("`name` = null", c.export());
}
@Test
void testSimpleNumber() {
QueryCriteria c = where(i("name")).is(5);
assertEquals("`name` = 5", c.export());
}
@Test
void testNotEqualCriteria() {
QueryCriteria c = where(i("name")).ne("Bubba");
assertEquals("`name` != \"Bubba\"", c.export());
}
@Test
void testChainedCriteria() {
QueryCriteria c = where(i("name")).is("Bubba").and(i("age")).lt(21).or(i("country")).is("Austria");
assertEquals("`name` = \"Bubba\" and `age` < 21 or `country` = \"Austria\"", c.export());
}
@Test
void testNestedAndCriteria() {
QueryCriteria c = where(i("name")).is("Bubba").and(where(i("age")).gt(12).or(i("country")).is("Austria"));
JsonArray parameters = JsonArray.create();
assertEquals(" (`name` = $1) and (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
}
@Test
void testNestedOrCriteria() {
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria"));
JsonArray parameters = JsonArray.create();
assertEquals(" (`name` = $1) or (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
}
@Test
void testNestedNotIn() {
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).and(i("country")).is("Austria"))
.and(where(i("state")).notIn(new String[] { "Alabama", "Florida" }));
JsonArray parameters = JsonArray.create();
assertEquals(" ( (`name` = $1) or (`age` > $2 and `country` = $3)) and (not( (`state` in $4) ))",
c.export(new int[1], parameters, null));
}
@Test
void testNestedNotIn2() {
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12)).and(where(i("state")).eq("1"));
JsonArray parameters = JsonArray.create();
assertEquals(" ( (`name` = $1) or (`age` > $2)) and (`state` = $3)", c.export(new int[1], parameters, null));
}
@Test
void testNestedNotIn3() {
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12)).and(i("state")).eq("1");
JsonArray parameters = JsonArray.create();
assertEquals(" (`name` = $1) or (`age` > $2) and `state` = $3", c.export(new int[1], parameters, null));
}
@Test
void testLt() {
QueryCriteria c = where(i("name")).lt("Couch");
assertEquals("`name` < \"Couch\"", c.export());
}
@Test
void testLte() {
QueryCriteria c = where(i("name")).lte("Couch");
assertEquals("`name` <= \"Couch\"", c.export());
}
@Test
void testGt() {
QueryCriteria c = where(i("name")).gt("Couch");
assertEquals("`name` > \"Couch\"", c.export());
}
@Test
void testGte() {
QueryCriteria c = where(i("name")).gte("Couch");
assertEquals("`name` >= \"Couch\"", c.export());
}
@Test
void testNe() {
QueryCriteria c = where(i("name")).ne("Couch");
assertEquals("`name` != \"Couch\"", c.export());
}
@Test
void testStartingWith() {
QueryCriteria c = where(i("name")).startingWith("Cou");
assertEquals("`name` like (\"Cou\"||\"%\")", c.export());
}
@Test
void testStartingWithExpr() {
QueryCriteria c = where(i("name")).startingWith(where(i("name")).plus("xxx"));
assertEquals("`name` like (((`name` || \"xxx\"))||\"%\")", c.export());
}
@Test
void testEndingWith() {
QueryCriteria c = where(i("name")).endingWith("ouch");
assertEquals("`name` like (\"%\"||\"ouch\")", c.export());
}
@Test
void testEndingWithExpr() {
QueryCriteria c = where(i("name")).endingWith(where(i("name")).plus("xxx"));
assertEquals("`name` like (\"%\"||((`name` || \"xxx\")))", c.export());
}
@Test
void testRegex() {
QueryCriteria c = where(i("name")).regex("C.*h");
assertEquals("regexp_like(`name`, \"C.*h\")", c.export());
}
@Test
void testContaining() {
QueryCriteria c = where(i("name")).containing("ouch");
assertEquals("contains(`name`, \"ouch\")", c.export());
}
@Test
void testNotContaining() {
QueryCriteria c = where(i("name")).notContaining("Elvis");
assertEquals("not (contains(`name`, \"Elvis\"))", c.export());
}
@Test
void testArrayContaining() {
QueryCriteria c = where(i("name")).arrayContaining("Elvis");
assertEquals("array_contains(`name`, \"Elvis\")", c.export());
}
@Test
void testLike() {
QueryCriteria c = where(i("name")).like("%ouch%");
assertEquals("`name` like \"%ouch%\"", c.export());
}
@Test
void testNotLike() {
QueryCriteria c = where(i("name")).notLike("%Elvis%");
assertEquals("not( ( (`name` like \"%Elvis%\")) )", c.export());
}
@Test
void testIsNull() {
QueryCriteria c = where(i("name")).isNull();
assertEquals("`name` is null", c.export());
}
@Test
void testIsNotNull() {
QueryCriteria c = where(i("name")).isNotNull();
assertEquals("`name` is not null", c.export());
}
@Test
void testIsMissing() {
QueryCriteria c = where(i("name")).isMissing();
assertEquals("`name` is missing", c.export());
}
@Test
void testIsNotMissing() {
QueryCriteria c = where(i("name")).isNotMissing();
assertEquals("`name` is not missing", c.export());
}
@Test
void testIsValued() {
QueryCriteria c = where(i("name")).isValued();
assertEquals("`name` is valued", c.export());
}
@Test
void testIsNotValued() {
QueryCriteria c = where(i("name")).isNotValued();
assertEquals("`name` is not valued", c.export());
}
@Test
void testBetween() {
QueryCriteria c = where(i("name")).between("Davis", "Gump");
assertEquals("`name` between \"Davis\" and \"Gump\"", c.export());
}
@Test
void testIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).in((Object) args); // the first arg is an array
assertEquals("`name` in [\"gump\",\"davis\"]", c.export());
JsonArray parameters = JsonArray.create();
assertEquals("`name` in $1", c.export(new int[1], parameters, null));
assertEquals(arrayToString(args), parameters.get(0).toString());
}
@Test
void testNotIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).notIn((Object) args); // the first arg is an array
assertEquals("not( (`name` in [\"gump\",\"davis\"]) )", c.export());
// this tests creating parameters from the args.
JsonArray parameters = JsonArray.create();
assertEquals("not( (`name` in $1) )", c.export(new int[1], parameters, null));
assertEquals(arrayToString(args), parameters.get(0).toString());
}
@Test
void testTrue() {
QueryCriteria c = where(i("name")).TRUE();
assertEquals("`name`", c.export());
}
@Test
void testFalse() {
QueryCriteria c = where(i("name")).FALSE();
assertEquals("not(`name`)", c.export());
}
@Test
void testKeys() {
N1QLExpression expression = N1QLExpression.x("");
assertEquals(" USE KEYS [\"a\",\"b\"]", expression.keys(Arrays.asList("a", "b")).toString());
assertEquals(" USE KEYS [\"a\"]", expression.keys(Arrays.asList("a")).toString());
assertEquals(" USE KEYS []", expression.keys(Arrays.asList()).toString());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066
void testCriteriaCorrectlyEscapedWhenUsingMetaOnLHS() {
final String bucketName = "sample-bucket";
final String version = "1611287177404088320";
QueryCriteria criteria = QueryCriteria.where(path(meta(escapedBucket(bucketName)), "cas")).eq(x(version));
assertEquals("META(`" + bucketName + "`).cas = " + x(version), criteria.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1066
void testCriteriaCorrectlyEscapedWhenUsingMetaOnRHS() {
final String bucketName = "sample-bucket";
final String version = "1611287177404088320";
QueryCriteria criteria = QueryCriteria.where(x(version)).eq(path(meta(escapedBucket(bucketName)), "cas"));
assertEquals(x(version) + " = META(`" + bucketName + "`).cas", criteria.export());
}
private String arrayToString(Object[] array) {
StringBuilder sb = new StringBuilder();
if (array != null) {
sb.append("[");
boolean first = true;
for (Object e : array) {
if (!first) {
sb.append(",");
}
first = false;
if (e instanceof Number)
sb.append(e);
else {
sb.append("\"");
sb.append(e);
sb.append("\"");
}
}
sb.append("]");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,751 @@
/*
* Copyright 2021-2022 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.core.query;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.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 java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.CollectionsConfig;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserJustLastName;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.domain.UserSubmissionProjected;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.UnambiguousTimeoutException;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetOptions;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions;
/**
* Query tests Theses tests rely on a cb server running This class tests collection support with
* inCollection(collection), inScope(scope) and withOptions(options). Testing without collections could also be done by
* this class simply by using scopeName = null and collectionName = null
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(CollectionsConfig.class)
class ReactiveCouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
Airport vie = new Airport("airports::vie", "vie", "low80");
ReactiveCouchbaseTemplate template;
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
// no-op
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inScope(scopeName).inCollection(collectionName).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(scopeName)
.inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inScope(otherScope).inCollection(otherCollection).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).all();
template = reactiveCouchbaseTemplate;
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
// query with REQUEST_PLUS to ensure that the remove has completed.
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
// then call the super method
super.afterEach();
}
@Test
void findByQueryAll() {
try {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).all();
for (User u : foundUsers) {
if (!(u.equals(user1) || u.equals(user2))) {
// somebody didn't clean up after themselves.
couchbaseTemplate.removeById().inCollection(collectionName).one(u.getId());
}
}
assertEquals(2, foundUsers.size());
TemporalAccessor auditTime = new AuditingDateTimeProvider().getNow().get();
long auditMillis = Instant.from(auditTime).toEpochMilli();
String auditUser = new NaiveAuditorAware().getCurrentAuditor().get();
for (User u : foundUsers) {
assertTrue(u.equals(user1) || u.equals(user2));
assertEquals(auditUser, u.getCreatedBy());
assertEquals(auditMillis, u.getCreatedDate());
assertEquals(auditUser, u.getLastModifiedBy());
assertEquals(auditMillis, u.getLastModifiedDate());
}
couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId());
reactiveCouchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId()).block();
} finally {
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
}
User usery = couchbaseTemplate.findById(User.class).inCollection(collectionName).one("userx");
assertNull(usery, "usery should be null");
User userz = reactiveCouchbaseTemplate.findById(User.class).inCollection(collectionName).one("userx").block();
assertNull(userz, "userz should be null");
}
@Test
void findByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
final List<User> foundUsers = couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).matching(specialUsers).all();
assertEquals(1, foundUsers.size());
}
@Test
void findByMatchingQueryProjected() {
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user.setRoles(Arrays.asList("role1", "role2"));
Address address = new Address();
address.setStreet("1234 Olcott Street");
user.setAddress(address);
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
user.setCourses(Arrays.asList(new Course(UUID.randomUUID().toString(), user.getId(), "581"),
new Course(UUID.randomUUID().toString(), user.getId(), "777")));
couchbaseTemplate.upsertById(UserSubmission.class).inCollection(collectionName).one(user);
Query daveUsers = new Query(QueryCriteria.where("username").like("dave"));
final List<UserSubmissionProjected> foundUserSubmissions = couchbaseTemplate.findByQuery(UserSubmission.class)
.as(UserSubmissionProjected.class).withConsistency(REQUEST_PLUS).inCollection(collectionName)
.matching(daveUsers).all();
assertEquals(1, foundUserSubmissions.size());
assertEquals(user.getUsername(), foundUserSubmissions.get(0).getUsername());
assertEquals(user.getId(), foundUserSubmissions.get(0).getId());
assertEquals(user.getCourses(), foundUserSubmissions.get(0).getCourses());
assertEquals(user.getAddress(), foundUserSubmissions.get(0).getAddress());
couchbaseTemplate.removeByQuery(UserSubmission.class).inCollection(collectionName).all();
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
Query specialUsers = new Query(QueryCriteria.where("firstname").like("special"));
final List<UserJustLastName> foundUsers = couchbaseTemplate.findByQuery(User.class).as(UserJustLastName.class)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).matching(specialUsers).all();
assertEquals(1, foundUsers.size());
final List<UserJustLastName> foundUsersReactive = reactiveCouchbaseTemplate.findByQuery(User.class)
.as(UserJustLastName.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).matching(specialUsers)
.all().collectList().block();
assertEquals(1, foundUsersReactive.size());
}
@Test
void removeByQueryAll() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).inScope(scopeName).inCollection(collectionName)
.all(Arrays.asList(user1, user2));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user2.getId()));
List<RemoveResult> result = couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS)
.inCollection(collectionName).all();
assertEquals(2, result.size(), "should have deleted user1 and user2");
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user2.getId()));
}
@Test
void removeByMatchingQuery() {
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
User specialUser = new User(UUID.randomUUID().toString(), "special", "special");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2, specialUser));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user2.getId()));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(specialUser.getId()));
Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special"));
couchbaseTemplate.removeByQuery(User.class).withConsistency(REQUEST_PLUS).inCollection(collectionName)
.matching(nonSpecialUsers).all();
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user2.getId()));
assertNotNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(specialUser.getId()));
}
@Test
void distinct() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
couchbaseTemplate.insertById(Airport.class).inCollection(collectionName).one(airport);
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(Airport.class)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
assertEquals(7, airports2.size());
// count( distinct { iata, icao } )
long count1 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "iata", "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).count();
assertEquals(7, count1);
// count( distinct (all fields in icaoClass)
Class icaoClass = (new Object() {
String iata;
String icao;
}).getClass();
long count2 = couchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {}).as(icaoClass)
.withConsistency(REQUEST_PLUS).inCollection(collectionName).count();
assertEquals(7, count2);
} finally {
couchbaseTemplate.removeById().inCollection(collectionName)
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet()));
}
}
@Test
void distinctReactive() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
String[] icaos = { "ic0", "ic1", "ic0", "ic1", "ic0", "ic1", "ic0" };
try {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, icaos[i] /* icao */);
reactiveCouchbaseTemplate.insertById(Airport.class).inCollection(collectionName).one(airport).block();
}
// distinct and count(distinct(...)) calls. use as() and consistentWith to verify fluent api
// as the fluent api for Distinct is tricky
// distinct icao
List<Airport> airports1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all().collectList().block();
assertEquals(2, airports1.size());
// distinct all-fields-in-Airport.class
List<Airport> airports2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] {})
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all().collectList().block();
assertEquals(7, airports2.size());
// count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(2, count1);
// count( distinct { iata, icao } )
Long count2 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "iata", "icao" })
.withConsistency(REQUEST_PLUS).inCollection(collectionName).count().block();
assertEquals(7, count2);
} finally {
reactiveCouchbaseTemplate.removeById().inCollection(collectionName)
.all(Arrays.stream(iatas).map((iata) -> "airports::" + iata).collect(Collectors.toSet())).collectList()
.block();
}
}
/**
* find . -name 'Exec*OperationSupport.java'|awk -F/ '{print $NF}'|sort| awk -F. '{print "* ", NR, ")",$1, ""}'<br>
* 1) ExecutableExistsByIdOperationSupport <br>
* 2) ExecutableFindByAnalyticsOperationSupport <br>
* 3) ExecutableFindByIdOperationSupport <br>
* 4) ExecutableFindByQueryOperationSupport <br>
* 5) ExecutableFindFromReplicasByIdOperationSupport <br>
* 6) ExecutableInsertByIdOperationSupport <br>
* 7) ExecutableRemoveByIdOperationSupport <br>
* 8) ExecutableRemoveByQueryOperationSupport <br>
* 9) ExecutableReplaceByIdOperationSupport <br>
* 10)ExecutableUpsertByIdOperationSupport <br>
*/
@Test
public void existsById() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("low7")).block();
try {
Boolean exists = template.existsById().inScope(scopeName).inCollection(collectionName).withOptions(existsOptions)
.one(saved.getId()).block();
assertTrue(exists, "Airport should exist: " + saved.getId());
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalytics() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("low8")).block();
try {
List<Airport> found = template.findByAnalytics(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).all().collectList().block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void findById() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("low9")).block();
try {
Airport found = template.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void findByQuery() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("lowa")).block();
try {
List<Airport> found = template.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(scopeName)
.inCollection(collectionName).withOptions(options).all().collectList().block();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void findFromReplicasById() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("lowb")).block();
try {
Airport found = template.findFromReplicasById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).any(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void insertById() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("lowc").withId(UUID.randomUUID().toString())).block();
try {
Airport found = template.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void removeById() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("lowd")).block();
RemoveResult removeResult = template.removeById().inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId()).block();
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQuery() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie.withIcao("lowe")).block();
List<RemoveResult> removeResults = template.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(scopeName).inCollection(collectionName).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all().collectList().block();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceById() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(insertOptions).one(vie.withIcao("lowe")).block();
Airport replaced = template.replaceById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("newIcao")).block();
try {
Airport found = template.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(replaced, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void upsertById() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.upsertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("lowf")).block();
try {
Airport found = template.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId()).block();
}
}
@Test
public void existsByIdOther() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowg")).block();
try {
Boolean exists = template.existsById().inScope(otherScope).inCollection(otherCollection)
.withOptions(existsOptions).one(saved.getId()).block();
assertTrue(exists, "Airport should exist: " + saved.getId());
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOther() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowh")).block();
try {
List<Airport> found = template.findByAnalytics(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).all().collectList().block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void findByIdOther() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowi")).block();
try {
Airport found = template.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void findByQueryOther() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowj")).block();
try {
List<Airport> found = template.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).all().collectList().block();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void findFromReplicasByIdOther() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowk")).block();
try {
Airport found = template.findFromReplicasById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).any(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void insertByIdOther() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("lowl").withId(UUID.randomUUID().toString())).block();
try {
Airport found = template.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void removeByIdOther() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lowm")).block();
RemoveResult removeResult = template.removeById().inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId()).block();
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQueryOther() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("lown")).block();
List<RemoveResult> removeResults = template.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.inScope(otherScope).inCollection(otherCollection).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all().collectList().block();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceByIdOther() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(insertOptions).one(vie.withIcao("lown")).block();
Airport replaced = template.replaceById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("newIcao")).block();
try {
Airport found = template.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(replaced, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void upsertByIdOther() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = template.upsertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("lowo")).block();
try {
Airport found = template.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId()).block();
assertEquals(saved, found);
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void existsByIdOptions() { // 1 - Options
ExistsOptions options = ExistsOptions.existsOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> template.existsById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()).block());
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOptions() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> template.findByAnalytics(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).all().collectList().block());
}
@Test
public void findByIdOptions() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> template.findById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()).block());
}
@Test
public void findByQueryOptions() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class,
() -> template.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).all().collectList().block());
}
@Test
public void findFromReplicasByIdOptions() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofNanos(1000));
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("low712")).block();
try {
Airport found = template.findFromReplicasById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).any(saved.getId()).block();
assertNull(found, "should not have found document in short timeout");
} finally {
template.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId()).block();
}
}
@Test
public void insertByIdOptions() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> template.insertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withId(UUID.randomUUID().toString())).block());
}
@Test
public void removeByIdOptions() { // 7 - options
Airport saved = template.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie.withIcao("732")).block();
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> template.removeById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()).block());
}
@Test
public void removeByQueryOptions() { // 8 - options
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class,
() -> template.removeByQuery(Airport.class).withConsistency(REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all().collectList().block());
}
@Test
public void replaceByIdOptions() { // 9 - options
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> template.replaceById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withIcao("newIcao")).block());
}
@Test
public void upsertByIdOptions() { // 10 - options
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> template.upsertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withIcao("760")).block());
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.UUID;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
/**
* @author Oliver Gierke
* @author Michael Reiche
*/
@Document
public class AbstractEntity {
@Id @GeneratedValue(strategy = GenerationStrategy.UNIQUE) private UUID id;
public AbstractEntity() {}
/**
* @return the id
*/
public UUID getId() {
return id;
}
public String id() {
return id.toString();
}
/**
* set the id
*/
public void setId(UUID id) {
this.id = id;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;
}
AbstractEntity that = (AbstractEntity) obj;
return this.id.equals(that.getId());
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2022 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.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.mapping.Field;
/**
* User entity for tests
*
* @author Michael Reiche
*/
@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER)
public abstract class AbstractUser extends ComparableEntity {
@Id protected String id;
protected String firstname;
protected String lastname;
@Field(AbstractingTypeMapper.SUBTYPE) protected String subtype;
public String getId() {
return id;
}
public String getFirstname() {
return firstname;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* AbstractUser Repository for tests
*
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface AbstractUserRepository extends CouchbaseRepository<AbstractUser, String> {
@Query("#{#n1ql.selectEntity} where (meta().id = $1)")
AbstractUser myFindById(String id);
List<AbstractUser> findByFirstname(String firstname);
Stream<User> findByLastname(String lastname);
List<User> findByFirstnameIn(String... firstnames);
List<User> findByFirstnameIn(JsonArray firstnames);
List<User> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<User> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2022 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.domain;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
/**
* MappingConverter that uses AbstractTypeMapper
*
* @author Michael Reiche
*/
public class AbstractingMappingCouchbaseConverter extends MappingCouchbaseConverter {
/**
* this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey while MappingCouchbaseConverter
* uses a DefaultCouchbaseTypeMapper typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey :
* TYPEKEY_DEFAULT);
*
* @param mappingContext
* @param typeKey - the typeKey to be used (normally "_class")
*/
public AbstractingMappingCouchbaseConverter(
final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext,
final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new AbstractingTypeMapper(typeKey);
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2022 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.domain;
import java.util.Collections;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.TypeAliasAccessor;
import org.springframework.data.couchbase.core.convert.CouchbaseTypeMapper;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.TypeInformation;
/**
* TypeMapper that leverages subtype
*
* @author Michael Reiche
*/
public class AbstractingTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
public static final String SUBTYPE = "subtype";
private final String typeKey;
public static class Type {
public static final String ABSTRACTUSER = "abstractuser", USER = "user", OTHERUSER = "otheruser";
}
/**
* Create a new type mapper with the type key.
*
* @param typeKey the typeKey to use.
*/
public AbstractingTypeMapper(final String typeKey) {
super(new CouchbaseDocumentTypeAliasAccessor(typeKey), (MappingContext) null, Collections
.singletonList(new org.springframework.data.couchbase.core.convert.TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
@Override
public String getTypeKey() {
return this.typeKey;
}
public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor<CouchbaseDocument> {
private final String typeKey;
public CouchbaseDocumentTypeAliasAccessor(final String typeKey) {
this.typeKey = typeKey;
}
@Override
public Alias readAliasFrom(final CouchbaseDocument source) {
String alias = (String) source.get(typeKey);
if (Type.ABSTRACTUSER.equals(alias)) {
String subtype = (String) source.get(AbstractingTypeMapper.SUBTYPE);
if (Type.OTHERUSER.equals(subtype)) {
alias = OtherUser.class.getName();
} else if (Type.USER.equals(subtype)) {
alias = User.class.getName();
} else {
throw new RuntimeException(
"no mapping for type " + SUBTYPE + "=" + subtype + " in type " + alias + " source=" + source);
}
}
return Alias.ofNullable(alias);
}
@Override
public void writeTypeTo(final CouchbaseDocument sink, final Object alias) {
if (typeKey != null) {
sink.put(typeKey, alias);
}
}
}
@Override
public Alias getTypeAlias(TypeInformation<?> info) {
return getAliasFor(info);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2020-2021 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.domain;
import com.couchbase.client.java.encryption.annotation.Encrypted;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
public class Address extends ComparableEntity {
private String street;
private String city;
// for N1qlJoin
private String id;
private String parentId;
public Address() {}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2020-2022 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.domain;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Scope;
/**
* @author Michael Reiche
*/
@Scope("dummy_scope") // set to non-existing scope. To use, scope must be determined by other means
// a different collection
@Collection("my_collection2")
public class AddressAnnotated extends Address {}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020-2021 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.domain;
import org.springframework.data.couchbase.core.mapping.Document;
import com.couchbase.client.java.encryption.annotation.Encrypted;
@Document
public class AddressWithEncStreet extends Address {
private @Encrypted String encStreet;
public AddressWithEncStreet() {}
public String getEncStreet() {
return encStreet;
}
public void setEncStreet(String encStreet) {
this.encStreet = encStreet;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2022 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.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.index.CompositeQueryIndex;
import org.springframework.data.couchbase.core.index.QueryIndexed;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
@CompositeQueryIndex(fields = { "id", "name desc" })
@CompositeQueryIndex(fields = { "id.something", "name desc" })
/**
* @author Michael Reiche
*/
public class Airline extends ComparableEntity {
@Id String id;
@QueryIndexed String name;
String hqCountry;
@PersistenceConstructor
public Airline(String id, String name, String hqCountry) {
this.id = id;
this.name = name;
this.hqCountry = hqCountry;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getHqCountry() {
return hqCountry;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2017-2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* @author Michael Reiche
*/
@Repository
public interface AirlineRepository extends CouchbaseRepository<Airline, String>,
QuerydslPredicateExecutor<Airline>, DynamicProxyable<AirlineRepository> {
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (name = $1)")
List<Airline> getByName(@Param("airline_name") String airlineName);
@Query("select meta().id as _ID, meta().cas as _CAS, #{#n1ql.bucket}.* from #{#n1ql.bucket} where #{#n1ql.filter} and (name = $1)")
List<Airline> getByName_3x(@Param("airline_name") String airlineName);
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2017-2022 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.domain;
import jakarta.validation.constraints.Max;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Expiration;
/**
* Airport entity
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
@TypeAlias("airport")
public class Airport extends ComparableEntity {
@Id String key;
String iata;
String icao;
@Version Number version;
@CreatedBy private String createdBy;
@Expiration private long expiration;
@Max(2) long size;
private long someNumber;
@PersistenceConstructor
public Airport(String key, String iata, String icao) {
this.key = key;
this.iata = iata;
this.icao = icao;
}
public String getId() {
return key;
}
public String getIata() {
return iata;
}
public String getIcao() {
return icao;
}
public long getExpiration() {
return expiration;
}
public Airport withId(String id) {
return new Airport(id, this.iata, this.icao);
}
public Airport withIcao(String icao) {
return new Airport(this.getId(), this.iata, icao);
}
public Airport withIata(String iata) {
return new Airport(this.getId(), iata, this.icao);
}
public Airport clearVersion() {
version = Long.valueOf(0);
return this;
}
public String getCreatedBy() {
return createdBy;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2017-2021 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.domain;
import java.util.Objects;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* AirportMini entity
*
* @author Michael Reiche
*/
@Document
public class AirportMini extends ComparableEntity {
@Id private String id;
private String iata;
private Address address;
@PersistenceConstructor
public AirportMini(final String id, final String iata) {
this.id = id;
this.iata = iata;
}
public String getId() {
return id;
}
public String getIata() {
return iata;
}
public void setIata(String iata) {
this.iata = iata;
}
@Override
public int hashCode() {
return Objects.hash(id, iata);
}
}

View File

@@ -0,0 +1,235 @@
/*
* Copyright 2017-2022 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.domain;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.mapping.Expiry;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Options;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Airport repository for testing <br>
* The DynamicProxyable interface exposes airportRepository.withScope(scope), withCollection() and withOptions() It's
* necessary on the repository object itself because the withScope() etc methods need to return an object of type
* AirportRepository so that one can code... airportRepository = airportRepository.withScope(scopeName) without having
* to cast the result.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> {
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findByIataInAndIcaoIn(java.util.Collection<String> size, java.util.Collection<String> color,
Pageable pageable);
// override an annotate with REQUEST_PLUS
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAll();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAllByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<AirportMini> getByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@ComposedMetaAnnotation(collection = "_default", timeoutMs = 1000)
Airport findByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByIata(Iata iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByIataIn(java.util.Collection<Iata> iatas);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByIataIn(Iata... iatas);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByIataIn(JsonArray iatas);
@Query("Select \"\" AS __id, 0 AS __cas, substr(iata,0,1) as iata, count(*) as someNumber FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} GROUP BY substr(iata,0,1)")
List<Airport> groupByIata();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findArchivedByIata(Iata iata);
// NOT_BOUNDED to test ScanConsistency
// @ScanConsistency(query = QueryScanConsistency.NOT_BOUNDED)
Airport iata(String iata);
@Query("#{#n1ql.selectEntity} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> getAllByIata(String iata);
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} and iata = $1 #{#n1ql.returning}")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<RemoveResult> deleteByIata(String iata);
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} and iata = $1 #{#n1ql.returning}")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Collection("bogus_collection")
List<RemoveResult> deleteByIataAnnotated(String iata);
@Query("SELECT __cas, * from #{#n1ql.bucket} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> getAllByIataNoID(String iata);
@Query("SELECT __id, * from #{#n1ql.bucket} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> getAllByIataNoCAS(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
long countByIataIn(String... iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
long countByIcaoAndIataIn(String icao, String... iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
long countByIcaoOrIataIn(String icao, String... iata);
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
long count();
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} #{#projectIds != null ? 'AND iata IN $1' : ''} "
+ " #{#planIds != null ? 'AND icao IN $2' : ''} #{#active != null ? 'AND false = $3' : ''} ")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countFancyExpression(@Param("projectIds") List<String> projectIds, @Param("planIds") List<String> planIds,
@Param("active") Boolean active);
@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)
Page<Airport> findAllByIataNot(String iata, Pageable pageable);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata != $1")
Page<Airport> getAllByIataNot(String iata, Pageable pageable);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT iata, \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<String> getStrings();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT length(iata), \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<Long> getLongs();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Query("SELECT iata, icao, \"\" as __id, 0 as __cas from #{#n1ql.bucket} WHERE #{#n1ql.filter} order by meta().id")
List<String[]> getStringArrays();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Optional<Airport> findByIdAndIata(String id, String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findDistinctIcaoBy();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findDistinctIcaoAndIataBy();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countDistinctIcaoAndIataBy();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countDistinctIcaoBy();
@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();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByKey(String id);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata between $1 and $2")
Slice<Airport> fetchSlice(String startIata, String iata, Pageable pageable);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND iata between $1 and $2")
Page<Airport> fetchPage(String startIata, String iata, Pageable pageable);
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
// @Meta
@Scope
@Collection
@ScanConsistency
@Expiry
@Options
public @interface ComposedMetaAnnotation {
// @AliasFor(annotation = Meta.class, attribute = "maxExecutionTimeMs")
// long execTime() default -1;
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = ScanConsistency.class, attribute = "analytics")
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = Scope.class, attribute = "value")
String scope() default DEFAULT_SCOPE;
@AliasFor(annotation = Collection.class, attribute = "value")
String collection() default DEFAULT_COLLECTION;
@AliasFor(annotation = Expiry.class, attribute = "expiry")
int expiry() default 0;
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
TimeUnit expiryUnit() default TimeUnit.SECONDS;
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
String expiryExpression() default "";
@AliasFor(annotation = Options.class, attribute = "timeoutMs")
long timeoutMs() default 0;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2022 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.domain;
import org.springframework.data.couchbase.repository.Collection;
/**
* AirportRepository with collection annotation
*
* @author Michael Reiche
*/
@Collection("my_collection2")
public interface AirportRepositoryAnnotated extends AirportRepository {}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2017-2021 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.domain;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.stereotype.Repository;
/**
* Airport repository for testing <br>
*
* @author Michael Reiche
*/
@Repository
public interface AirportRepositoryScanConsistencyTest extends CouchbaseRepository<Airport, String> {
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2021 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.domain;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
/**
* @author Michael Reiche
*/
@Document()
@Data
@NoArgsConstructor
public class AssessmentDO {
@Id @GeneratedValue(strategy = GenerationStrategy.USE_ATTRIBUTES) private String documentId;
@Field @IdAttribute private long eventTimestamp;
@Field("docType") private String documentType;
@Field private String id;
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2022 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.domain;
import static com.couchbase.client.java.query.QueryOptions.queryOptions;
import static java.nio.charset.StandardCharsets.UTF_8;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.tls.HandshakeCertificates;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.couchbase.client.core.env.IoConfig;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.manager.query.CreatePrimaryQueryIndexOptions;
import com.couchbase.client.java.query.QueryResult;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Sample code for connecting to Capella through both the control-plane and the data-plane. An Access Key and a Secret
* Key are required and a bucket named "my_bucket" on the 'last' cluster. <br>
* 1) Create a cluster that has data, index and query nodes. <br>
* 2) Cluster -> Connectivity : allow your client ip address (or all ip address 0/0.0.0.0)<br>
* 3) Create a user "user" in the cluster with password "Couch0base!" and Read/Write access to all buckets <br>
* 4) Create a bucket named "my_bucket" <br>
* 5) Get your access key from API Keys. The secret key is available only when the key is generated. If you have not
* saved it, then generate a new key and save the secret key. <br>
*/
public class CapellaConnectSample {
static final String cbc_access_key = "3gcpgyTBzOetdETYxOAtmLYBe3f9ZSVN"; // replace with your access key and...
static final String cbc_secret_key = "PWiACuJIZUlv0fCZaIQbhI44NDXVZCDdRBbpdaWlACioN7jkuOINCUVrU2QL1jVO"; // secret key
// Update this to your cluster
static String bucketName = "my_bucket";
static String username = "user";
static String password = "Couch0base!";
// User Input ends here.
static final String hostname = "cloudapi.cloud.couchbase.com";
static final HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()/*.addInsecureHost(hostname)*/.build();
static final OkHttpClient httpClient = new OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).build();
protected static final ObjectMapper MAPPER = new ObjectMapper();
static final String authorizationHeaderLabel = "Authorization";
static final String timestampHeaderLabel = "Couchbase-Timestamp";
public static void main(String... args) {
String endpoint = null; // "cb.zsibzkbgllfbcj8g.cloud.couchbase.com";
List<String> clusterIds = getClustersControlPlane();
// the following loop assumes that the desired cluster is the last one in the list.
// If this is not the case, then the endpoint for the desired cluster must be selected.
for (String id : clusterIds) {
endpoint = getClusterControlPlane(id);
}
ClusterEnvironment env = ClusterEnvironment.builder()
.securityConfig(SecurityConfig.enableTls(true)/*.trustManagerFactory(InsecureTrustManagerFactory.INSTANCE)*/)
.ioConfig(IoConfig.enableDnsSrv(true)).build();
// Initialize the Connection
Cluster cluster = Cluster.connect(endpoint, ClusterOptions.clusterOptions(username, password).environment(env));
Bucket bucket = cluster.bucket(bucketName);
bucket.waitUntilReady(Duration.parse("PT10S"));
Collection collection = bucket.defaultCollection();
cluster.queryIndexes().createPrimaryIndex(bucketName,
CreatePrimaryQueryIndexOptions.createPrimaryQueryIndexOptions().ignoreIfExists(true));
// Create a JSON Document
JsonObject arthur = JsonObject.create().put("name", "Arthur").put("email", "kingarthur@couchbase.com")
.put("interests", JsonArray.from("Holy Grail", "African Swallows"));
// Store the Document
collection.upsert("u:king_arthur", arthur);
// Load the Document and print it
// Prints Content and Metadata of the stored Document
System.err.println(collection.get("u:king_arthur"));
// Perform a N1QL Query
QueryResult result = cluster.query(String.format("SELECT name FROM `%s` WHERE $1 IN interests", bucketName),
queryOptions().parameters(JsonArray.from("African Swallows")));
// Print each found Row
for (JsonObject row : result.rowsAsObject()) {
System.err.println(row);
}
cluster.disconnect();
}
public static List<String> getClustersControlPlane() {
List<String> clusterIds = new ArrayList<>();
Map<String, Object> decoded = doRequest(hostname, "GET", "/v3/clusters");
HashMap data = (HashMap) decoded.get("data");
List<Map> items = (List<Map>) data.get("items");
for (Map m : items) {
clusterIds.add((String) m.get("id"));
}
return clusterIds;
}
// the methods below are required only to get the endpoint (host)
public static String getClusterControlPlane(String clusterId) {
String endpointsSrv;
Map<String, Object> decoded = doRequest(hostname, "GET", "/v3/clusters/" + clusterId);
endpointsSrv = (String) decoded.get("endpointsSrv");
return endpointsSrv;
}
private static Map<String, Object> doRequest(String hostname, String cbc_api_method, String cbc_api_endpoint) {
Map<String, Object> decoded;
String responseString;
try {
String cbc_api_now = Long.toString(System.currentTimeMillis());
String authorizationValue = getApiSignature(cbc_api_method, cbc_api_endpoint, cbc_api_now);
String urlString = "https://" + hostname + cbc_api_endpoint;
System.err.println("curl --header \"" + authorizationHeaderLabel + ": " + authorizationValue + "\" --header \""
+ timestampHeaderLabel + ": " + cbc_api_now + "\" " + urlString);
Response response = httpClient.newCall(new Request.Builder().header(authorizationHeaderLabel, authorizationValue)
.header(timestampHeaderLabel, cbc_api_now).url(urlString).build()).execute();
responseString = response.body().string();
System.err.println(responseString);
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
try {
decoded = (Map<String, Object>) MAPPER.readValue(responseString.getBytes(UTF_8), Map.class);
} catch (IOException e) {
throw new RuntimeException("Error decoding, raw: " + responseString, e);
}
return decoded;
}
private static String getApiSignature(String cbc_api_method, String cbc_api_endpoint, String cbc_api_now)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String cbc_api_message = cbc_api_method + '\n' + cbc_api_endpoint + '\n' + cbc_api_now;
return "Bearer " + cbc_access_key + ':' + new String(Base64.getEncoder()
.encode(hmac("hmacSHA256", cbc_secret_key.getBytes("utf-8"), cbc_api_message.getBytes("utf-8"))));
}
static byte[] hmac(String algorithm, byte[] key, byte[] message)
throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(message);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2022 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.domain;
/**
* Config to be used for testing scopes and collections.
*
* @author Michael Reiche
*/
public class CollectionsConfig extends Config {
@Override
public String getScopeName() {
return "my_scope";
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 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.domain;
import com.couchbase.mock.deps.com.google.gson.Gson;
import com.couchbase.mock.deps.com.google.gson.GsonBuilder;
/**
* Comparable entity base class for tests
*
* @author Michael Reiche
*/
public class ComparableEntity {
/**
* equals() method that relies on toString()
*
* @param that
* @return
* @throws RuntimeException
*/
@Override
public boolean equals(Object that) throws RuntimeException {
if (this == that) {
return true;
}
if (that == null
|| !(this.getClass().isAssignableFrom(that.getClass()) || that.getClass().isAssignableFrom(this.getClass()))) {
return false;
}
return this.toString().equals(that.toString());
}
public String toString() throws RuntimeException {
Gson gson = new GsonBuilder().create();
return gson.toJson(this);
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2012-2021 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.domain;
import java.lang.reflect.InvocationTargetException;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JacksonTransformers;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @author Jorge Rodriguez Martin
* @since 3.0
*/
@Configuration
@EnableCouchbaseRepositories
@EnableReactiveCouchbaseRepositories
@EnableCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
@EnableReactiveCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
@EnableCaching
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";
String username = "Administrator";
String password = "password";
String connectionString = "127.0.0.1";
// if running a clusterAwareIntegrationTests, use those properties
static Class clusterAware = null;
static {
try {
clusterAware = Class.forName("org.springframework.data.couchbase.util.ClusterAwareIntegrationTests");
if (clusterAware.getMethod("config").invoke(null) == null)
clusterAware = null;
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
String clusterGet(String methodName, String defaultValue) {
if (clusterAware != null) {
try {
return (String) clusterAware.getMethod(methodName).invoke(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return defaultValue;
}
@Override
public String getConnectionString() {
return clusterGet("connectionString", connectionString);
}
@Override
public String getUserName() {
return clusterGet("username", username);
}
@Override
public String getPassword() {
return clusterGet("password", password);
}
@Override
public String getBucketName() {
return clusterGet("bucketName", bucketname);
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (getConnectionString().contains("cloud.couchbase.com")) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Bean(name = "auditorAwareRef")
public NaiveAuditorAware testAuditorAware() {
return new NaiveAuditorAware();
}
@Bean(name = "reactiveAuditorAwareRef")
public ReactiveNaiveAuditorAware testReactiveAuditorAware() {
return new ReactiveNaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
@Override
public void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping baseMapping) {
try {
// comment out references to 'protected' and 'mybucket' - they are only to show how multi-bucket would work
// ReactiveCouchbaseTemplate personTemplate = myReactiveCouchbaseTemplate(myCouchbaseClientFactory("protected"),
// (MappingCouchbaseConverter) (baseMapping.getDefault().getConverter()));
// baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket
// ReactiveCouchbaseTemplate userTemplate = myReactiveCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),
// (MappingCouchbaseConverter) (baseMapping.getDefault().getConverter()));
// baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket"
// everything else goes in getBucketName() ( which is travel-sample )
} catch (Exception e) {
throw e;
}
}
@Override
public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) {
try {
// comment out references to 'protected' and 'mybucket' - they are only to show how multi-bucket would work
// CouchbaseTemplate personTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("protected"),
// (MappingCouchbaseConverter) (baseMapping.getDefault().getConverter()));
// baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket
// MappingCouchbaseConverter cvtr = (MappingCouchbaseConverter)baseMapping.getDefault().getConverter();
// CouchbaseTemplate userTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),
// (MappingCouchbaseConverter) (baseMapping.getDefault().getConverter()));
// baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket"
// everything else goes in getBucketName() ( which is travel-sample )
} catch (Exception e) {
throw e;
}
}
// do not use reactiveCouchbaseTemplate for the name of this method, otherwise the value of that bean
// will be used instead of the result of this call (the client factory arg is different)
public ReactiveCouchbaseTemplate myReactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter,
new JacksonTranslationService(), getDefaultConsistency());
}
// do not use couchbaseTemplate for the name of this method, otherwise the value of that been
// will be used instead of the result from this call (the client factory arg is different)
public CouchbaseTemplate myCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, new JacksonTranslationService(),
getDefaultConsistency());
}
// do not use couchbaseClientFactory for the name of this method, otherwise the value of that bean will
// will be used instead of this call being made ( bucketname is an arg here, instead of using bucketName() )
public CouchbaseClientFactory myCouchbaseClientFactory(String bucketName) {
return new SimpleCouchbaseClientFactory(getConnectionString(), authenticator(), bucketName);
}
// convenience constructor for tests
public MappingCouchbaseConverter mappingCouchbaseConverter() {
MappingCouchbaseConverter converter = null;
try {
// MappingCouchbaseConverter relies on a SimpleInformationMapper
// that has an getAliasFor(info) that just returns getType().getName().
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
// use the DocumentType annotation
converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(customConversions()), typeKey());
} catch (Exception e) {
throw new RuntimeException(e);
}
return converter;
}
/* This uses a CustomMappingCouchbaseConverter instead of MappingCouchbaseConverter */
@Override
@Bean(name = "mappingCouchbaseConverter")
public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext,
CouchbaseCustomConversions couchbaseCustomConversions /* there is a customConversions() method bean */) {
// MappingCouchbaseConverter relies on a SimpleInformationMapper
// that has an getAliasFor(info) that just returns getType().getName().
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
// use the DocumentType annotation
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext, typeKey());
converter.setCustomConversions(couchbaseCustomConversions);
return converter;
}
@Override
@Bean(name = "couchbaseTranslationService")
public TranslationService couchbaseTranslationService() {
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
jacksonTranslationService.afterPropertiesSet();
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return jacksonTranslationService;
}
@Bean
public CouchbaseCacheManager cacheManager(CouchbaseTemplate couchbaseTemplate) throws Exception {
CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder
.fromConnectionFactory(couchbaseTemplate.getCouchbaseClientFactory());
return builder.build();
}
@Override
public String typeKey() {
return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter
}
static String scopeName = null;
@Override
protected String getScopeName() {
return scopeName;
}
public static void setScopeName(String scopeName) {
Config.scopeName = scopeName;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 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.domain;
import org.springframework.data.annotation.Id;
import java.lang.reflect.Field;
/**
* Course entity for tests
*
* @author Michael Reiche
*/
public class Course extends ComparableEntity {
@Id private final String id;
private final String userId;
private final String room;
public Course(String id, String userId, String room) {
this.id = id;
this.userId = userId;
this.room = room;
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2021 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.domain;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
/**
* this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey while MappingCouchbaseConverter
* uses a DefaultCouchbaseTypeMapper typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey :
* TYPEKEY_DEFAULT);
*
* @param mappingContext
* @param typeKey - the typeKey to be used (normally "_class")
*/
public CustomMappingCouchbaseConverter(
final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext,
final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}
}

View File

@@ -0,0 +1,278 @@
/*
* Copyright 2021-2022 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.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.ParallelFlux;
import reactor.core.scheduler.Schedulers;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterEach;
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.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.data.util.Pair;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.ReactiveCollection;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.GetResult;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryProfile;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* @author Michael Reiche
*/
@SpringJUnitConfig(FluxIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class FluxIntegrationTests extends JavaIntegrationTests {
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeEach
@Override
public void beforeEach() {
/**
* The couchbaseTemplate inherited from JavaIntegrationTests uses org.springframework.data.couchbase.domain.Config
* It has typeName = 't' (instead of _class). Don't use it.
*/
ApplicationContext ac = new AnnotationConfigApplicationContext(FluxIntegrationTests.Config.class);
couchbaseTemplate = (CouchbaseTemplate) ac.getBean(BeanNames.COUCHBASE_TEMPLATE);
reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(BeanNames.REACTIVE_COUCHBASE_TEMPLATE);
collection = couchbaseTemplate.getCouchbaseClientFactory().getBucket().defaultCollection();
rCollection = couchbaseTemplate.getCouchbaseClientFactory().getBucket().reactive().defaultCollection();
for (String k : keyList) {
couchbaseTemplate.getCouchbaseClientFactory().getBucket().defaultCollection().upsert(k,
JsonObject.create().put("x", k));
}
super.beforeEach();
}
@AfterEach
public void afterEach() {
couchbaseTemplate.removeByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
super.afterEach();
for (String k : keyList) {
couchbaseTemplate.getCouchbaseClientFactory().getBucket().defaultCollection().remove(k);
}
}
static List<String> keyList = Arrays.asList("a", "b", "c", "d", "e");
static Collection collection;
static ReactiveCollection rCollection;
@Autowired ReactiveAirportRepository reactiveAirportRepository; // intellij flags "Could not Autowire", runs ok.
AtomicInteger rCat = new AtomicInteger(0);
AtomicInteger rFlat = new AtomicInteger(0);
@Test
public void concatMapCB() throws Exception {
System.out.println("Start concatMapCB");
System.out.println("\n******** Using concatMap() *********");
ParallelFlux<GetResult> concat = Flux.fromIterable(keyList).parallel(2).runOn(Schedulers.parallel())
.concatMap(item -> cbGet(item)
/* rCollection.get(item) */.doOnSubscribe((x) -> System.out.println(" +" + rCat.incrementAndGet()))
.doOnTerminate(() -> System.out.println(" -" + rCat.decrementAndGet())));
System.out.println(concat.sequential().collectList().block());
}
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public void cbse() {
LinkedList<LinkedList<Airport>> listOfLists = new LinkedList<>();
Airport a = new Airport(UUID.randomUUID().toString(), "iata", "lowp");
String last = null;
for (int i = 0; i < 5; i++) {
LinkedList<Airport> list = new LinkedList<>();
for (int j = 0; j < 10; j++) {
list.add(a.withId(UUID.randomUUID().toString()));
last = a.getId();
}
listOfLists.add(list);
}
Flux<Object> af = Flux.fromIterable(listOfLists).concatMap(catalogToStore -> Flux.fromIterable(catalogToStore)
.parallel(4).runOn(Schedulers.parallel()).concatMap((entity) -> reactiveAirportRepository.save(entity)));
List<Object> saved = af.collectList().block();
System.out.println("results.size() : " + saved.size());
String statement = "select * from `" + /*config().bucketname()*/ "_default" + "` where META().id >= '" + last + "'";
System.out.println("statement: " + statement);
try {
QueryResult qr = couchbaseTemplate.getCouchbaseClientFactory().getScope().query(statement,
QueryOptions.queryOptions().profile(QueryProfile.PHASES));
List<RemoveResult> rr = couchbaseTemplate.removeByQuery(Airport.class)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS)).all();
System.out.println(qr.metaData().profile().get());
} catch (Exception e) {
e.printStackTrace();
throw e;
}
List<Airport> airports = reactiveAirportRepository.findAll().collectList().block();
assertEquals(0, airports.size(), "should have been all deleted");
}
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY }, clusterTypes = ClusterType.MOCKED)
public void pairIdAndResult() {
LinkedList<Airport> list = new LinkedList<>();
Airport a = new Airport(UUID.randomUUID().toString(), "iata", "lowp");
for (int i = 0; i < 5; i++) {
list.add(a.withId(UUID.randomUUID().toString()));
}
Flux<Object> af = Flux.fromIterable(list).concatMap((entity) -> reactiveAirportRepository.save(entity));
List<Object> saved = af.collectList().block();
System.out.println("results.size() : " + saved.size());
Flux<Pair<String, Mono<Airport>>> pairFlux = Flux.fromIterable(list)
.map((airport) -> Pair.of(airport.getId(), reactiveAirportRepository.findById(airport.getId())));
List<Pair<String, Mono<Airport>>> airportPairs = pairFlux.collectList().block();
for (Pair<String, Mono<Airport>> airportPair : airportPairs) {
System.out.println("id: " + airportPair.getFirst() + " airport: " + airportPair.getSecond().block());
}
}
@Test
public void flatMapCB() throws Exception {
System.out.println("Start flatMapCB");
ParallelFlux<GetResult> concat = Flux.fromIterable(keyList).parallel(2).runOn(Schedulers.parallel())
.flatMap(item -> cbGet(item) /* rCollection.get(item) */
.doOnSubscribe((x) -> System.out.println(" +" + rCat.incrementAndGet()))
.doOnTerminate(() -> System.out.println(" -" + rCat.decrementAndGet())));
System.out.println(concat.sequential().collectList().block());
}
@Test
public void flatMapSyncCB() throws Exception {
System.out.println("Start flatMapSyncCB");
System.out.println("\n******** Using flatSyncMap() *********");
ParallelFlux<GetResult> concat = Flux.fromIterable(keyList).parallel(2).runOn(Schedulers.parallel())
.flatMap(item -> Flux.just(cbGetSync(item) /* collection.get(item) */));
System.out.println(concat.sequential().collectList().block());
;
}
@Test
public void flatMapVsConcatMapCB2() throws Exception {
System.out.println("Start flatMapCB2");
System.out.println("\n******** Using flatMap() *********");
ParallelFlux<GetResult> flat = Flux.fromIterable(keyList).parallel(1).runOn(Schedulers.parallel())
.flatMap(item -> rCollection.get(item).doOnSubscribe((x) -> System.out.println(" +" + rCat.incrementAndGet()))
.doOnTerminate(() -> System.out.println(" -" + rCat.getAndDecrement())));
System.out.println(flat.sequential().collectList().block());
System.out.println("Start concatMapCB");
System.out.println("\n******** Using concatMap() *********");
ParallelFlux<GetResult> concat = Flux.fromIterable(keyList).parallel(2).runOn(Schedulers.parallel())
.concatMap(item -> cbGet(item).doOnSubscribe((x) -> System.out.println(" +" + rCat.incrementAndGet()))
.doOnTerminate(() -> System.out.println(" -" + rCat.getAndDecrement())));
System.out.println(concat.sequential().collectList().block());
;
}
static Random r = new Random();
static void sleep(long sleepMs) {
try {
int random = Math.abs(r.nextInt() % 1000);
Thread.sleep(sleepMs * random);
} catch (InterruptedException e) {}
}
AtomicInteger cbCount = new AtomicInteger();
Mono<GetResult> cbGet(String id) {
// System.out.println(" =" + id);
return rCollection.get(id);
}
GetResult cbGetSync(String id) {
// System.out.println(id + " +" + rCat.incrementAndGet());
GetResult result = collection.get(id);
// System.out.println(id + " -" + rCat.getAndDecrement());
return result;
}
static String tab(int len) {
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++)
sb.append(" ");
return sb.toString();
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.data.couchbase.domain;
public enum Iata {
vie, // must be lower-case to match "vie" as airport.iata is always specified in lowercase
xxx
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-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.domain;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
public class Library {
@Id String id;
List<String> books;
public Library(String id, List<String> books) {
this.id = id;
this.books = books;
}
public String getId() {
return id;
}
public List<String> getBooks() {
return books;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Library library = (Library) o;
if (id != null ? !id.equals(library.id) : library.id != null)
return false;
return books != null ? books.equals(library.books) : library.books == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (books != null ? books.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-2022 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.domain;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.stereotype.Repository;
/**
* Library Repository for tests
*
* @author Andrea Torlaschi
*/
@Repository
public interface LibraryRepository extends CouchbaseRepository<Library, String> {
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-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.domain;
import java.util.Optional;
import org.springframework.data.domain.AuditorAware;
// These are the classes that would be used for a real getCurrentAuditor() implementation
//import org.springframework.security.core.Authentication;
//import org.springframework.security.core.context.SecurityContextHolder;
//import org.springframework.security.core.userdetails.User;
/**
* This class returns a string that represents the current user
*
* @author Michael Reiche
* @since 3.0
*/
public class NaiveAuditorAware implements AuditorAware<String> {
static public final String AUDITOR = "nonreactive_auditor";
private Optional<String> auditor = Optional.of(AUDITOR);
@Override
public Optional<String> getCurrentAuditor() {
return auditor;
}
public void setAuditor(String auditor) {
this.auditor = Optional.of(auditor);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2022 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.domain;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* OtherUser entity for tests. Both User and OtherUser extend AbstractUser
*
* @author Michael Reiche
*/
@Document
@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER)
public class OtherUser extends AbstractUser {
@PersistenceConstructor
public OtherUser(final String id, final String firstname, final String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.subtype = AbstractingTypeMapper.Type.OTHERUSER;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.domain.Persistable;
import org.springframework.lang.Nullable;
/**
* Person entity for tests.
*
* @author Michael Reiche
*/
@Document
public class Person extends AbstractEntity implements Persistable<Object> {
Optional<String> firstname;
@Nullable Optional<String> lastname;
@CreatedBy private String creator;
@LastModifiedBy private String lastModifiedBy;
@LastModifiedDate private long lastModification;
@CreatedDate private long creationDate;
@Version private long version;
@Nullable @Field("nickname") private String middlename;
@Nullable @Field(name = "prefix") private String salutation;
private Address address;
@Transient private boolean isNew;
public Person() {
setId(UUID.randomUUID());
}
public Person(String firstname, String lastname) {
this();
setFirstname(firstname);
setLastname(lastname);
setMiddlename("Nick");
isNew(true);
}
public Person(int id, String firstname, String lastname) {
this(firstname, lastname);
setId(new UUID(id, id));
}
public Person(UUID id, String firstname, String lastname) {
this(firstname, lastname);
setId(id);
}
static String optional(String name, Optional<String> obj) {
if (obj != null) {
if (obj.isPresent()) {
return (" " + name + ": '" + obj.get() + "'");
} else {
return " " + name + ": null";
}
}
return "";
}
public String getFirstname() {
return firstname.get();
}
public void setFirstname(String firstname) {
this.firstname = firstname == null ? null : (Optional.ofNullable(firstname.equals("") ? null : firstname));
}
public void setFirstname(Optional<String> firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname.get();
}
public void setLastname(String lastname) {
this.lastname = lastname == null ? null : (Optional.ofNullable(lastname.equals("") ? null : lastname));
}
public void setLastname(Optional lastname) {
this.lastname = lastname;
}
public String getMiddlename() {
return middlename;
}
public String getSalutation() {
return salutation;
}
public void setMiddlename(String middlename) {
this.middlename = middlename;
}
public void setSalutation(String salutation) {
this.salutation = salutation;
}
public long getVersion() {
return version;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Person : {\n");
sb.append(" id : " + getId());
sb.append(optional(", firstname", firstname));
sb.append(optional(", lastname", lastname));
if (middlename != null)
sb.append(", middlename : '" + middlename + "'");
sb.append(", version : " + version);
if (creator != null) {
sb.append(", creator : " + creator);
}
if (creationDate != 0) {
sb.append(", creationDate : " + creationDate);
}
if (lastModifiedBy != null) {
sb.append(", lastModifiedBy : " + lastModifiedBy);
}
if (lastModification != 0) {
sb.append(", lastModification : " + lastModification);
}
if (getAddress() != null) {
sb.append(", address : " + getAddress().toString());
}
sb.append("\n}");
return sb.toString();
}
public Person withFirstName(String firstName) {
Person p = new Person(this.getId(), firstName, this.getLastname());
p.version = version;
return p;
}
// A with-er that returns the same object ??
public Person withVersion(Long version) {
// Person p = new Person(this.getId(), this.getFirstname(), this.getLastname());
this.version = version;
return this;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
Person that = (Person) obj;
return this.getId().equals(that.getId()) && this.getFirstname().equals(that.getFirstname())
&& this.getLastname().equals(that.getLastname()) && this.getMiddlename().equals(that.getMiddlename());
}
@Override
public boolean isNew() {
return isNew;
}
public void isNew(boolean isNew) {
this.isNew = isNew;
}
public Person withIdFirstname() {
return this.withFirstName(getId().toString());
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import java.util.UUID;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* @author Michael Reiche
*/
public interface PersonRepository extends CouchbaseRepository<Person, String>, DynamicProxyable<PersonRepository> {
/*
* These methods are exercised in HomeController of the test spring-boot DemoApplication
*/
public List<Person> findByLastname(String lastname);
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = $last")
public List<Person> any(@Param("last") String any);
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = 'McIntyre'")
public List<Person> none();
@Query("#{#n1ql.selectEntity} where firstname = 'Reba' and lastname = $1")
public List<Person> one(@Param("last") String any);
@Query("#{#n1ql.selectEntity} where lastname = $2 and firstname = $1")
public List<Person> two(@Param("one") String one, @Param("two") String two);
@Query("#{#n1ql.selectEntity} where lastname in ( $1 )")
public List<Person> lastnameIn(@Param("lastnames") String[] lastnames);
public Person findByFirstname(String firstname);
public Person findFromReplicasByFirstname(String firstname);
public List<Person> findFromReplicasById(String id);
public List<Person> findByFirstnameLike(String firstname);
public List<Person> findByFirstnameIsNull();
public List<Person> findByFirstnameIsNotNull();
public List<Person> findByFirstnameNotLike(String firstname);
public List<Person> findByFirstnameStartingWith(String firstname);
public List<Person> findByFirstnameEndingWith(String firstname);
public List<Person> findByFirstnameContaining(String firstname);
public List<Person> findByFirstnameNotContaining(String firstname);
public List<Person> findByFirstnameBetween(String firstname1, String firstname2);
public List<Person> findByFirstnameIn(String... firstnames);
public List<Person> findByFirstnameNotIn(String... firstnames);
public List<Person> findByFirstnameTrue(Object... o);
public List<Person> findByFirstnameFalse(Object... o);
List<Person> findByFirstnameAndLastname(String firstname, String lastname);
List<Person> findByFirstnameOrLastname(String firstname, String lastname);
<S extends Person> S save(S var1);
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
Person findById(UUID var1);
boolean existsById(UUID var1);
List<Person> findAll();
long count();
void deleteById(UUID var1);
void delete(Person var1);
void deleteAll(Iterable<? extends Person> var1);
void deleteAll();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Person> findByAddressStreet(String street);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Person> findByMiddlename(String nickName);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Person> findBySalutation(String prefix);
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 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.domain;
import lombok.Value;
import lombok.With;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
/**
* PersonValue entity for tests
*
* @author Michael Reiche
*/
@Value
@Document
public class PersonValue {
@Id @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
@With String id;
@Version
@With
long version;
@Field String firstname;
@Field String lastname;
public PersonValue(String id, long version, String firstname, String lastname) {
this.id = id;
this.version = version;
this.firstname = firstname;
this.lastname = lastname;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PersonValue : {");
sb.append(" id : " + getId());
sb.append(", version : " + version);
sb.append(", firstname : " + firstname);
sb.append(", lastname : " + lastname);
sb.append(" }");
return sb.toString();
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 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.domain;
import org.springframework.data.repository.CrudRepository;
/**
* PersonValue repository for tests
*
* @author Michael Reiche
*/
public interface PersonValueRepository extends CrudRepository<PersonValue, String> {
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* Person entity without a an @Version property
*
* @author Michael Reiche
*/
@Document
public class PersonWithoutVersion extends AbstractEntity {
Optional<String> firstname;
Optional<String> lastname;
public PersonWithoutVersion() {
firstname = Optional.empty();
lastname = Optional.empty();
}
public PersonWithoutVersion(String firstname, String lastname) {
this.firstname = Optional.of(firstname);
this.lastname = Optional.of(lastname);
setId(UUID.randomUUID());
}
public PersonWithoutVersion(UUID id, String firstname, String lastname) {
this.firstname = Optional.of(firstname);
this.lastname = Optional.of(lastname);
setId(id);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
/**
* @author Michael Reiche
*/
@Repository
public interface ReactiveAirlineRepository extends ReactiveCouchbaseRepository<Airline, String> {
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (name = $1)")
List<User> getByName(@Param("airline_name") String airlineName);
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2017-2022 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.domain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* template class for Reactive Couchbase operations
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
public interface ReactiveAirportRepository
extends ReactiveCouchbaseRepository<Airport, String>, DynamicProxyable<ReactiveAirportRepository> {
@Query("SELECT META(#{#n1ql.bucket}).id AS __id, META(#{#n1ql.bucket}).cas AS __cas, meta().id as id FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} #{[1]}")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<String> findIdByDynamicN1ql(String docType, String queryStatement);
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAll();
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Void> deleteAll();
@Override
Mono<Airport> save(Airport a);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIata(Mono<String> iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> iata(String iata);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
Flux<Airport> findAllPoliciesByApplicableTypes(String state, JsonArray applicableTypes);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} and icao != $1 ORDER BY effectiveDateTime DESC LIMIT 1")
Mono<Airport> findPolicySnapshotByPolicyIdAndEffectiveDateTime(String policyId, long effectiveDateTime);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} ORDER BY effectiveDateTime DESC")
Flux<Airport> findPolicySnapshotAll();
@Query("#{#n1ql.selectEntity} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> getAllByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Long> countByIataIn(String... iatas);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Long> countByIcaoAndIataIn(String icao, String... iatas);
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Long> count();
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> findById(String var1);
// use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIataLike(String iata, final PageRequest page);
// use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIataLike(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> findByIata(String iata);
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} and iata = $1 #{#n1ql.returning}")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<RemoveResult> deleteByIata(String iata);
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} and iata = $1 #{#n1ql.returning}")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@Collection("bogus_collection")
Flux<RemoveResult> deleteByIataAnnotated(String iata);
// This is not efficient. See findAllByIataLike for efficient reactive paging
default public Mono<Page<Airport>> findAllAirportsPaged(Pageable pageable) {
return count().flatMap(airportCount -> {
return findAll(pageable.getSort())
.buffer(pageable.getPageSize(), (pageable.getPageNumber() * pageable.getPageSize()))
.elementAt(pageable.getPageNumber(), new ArrayList<>())
.map(airports -> new PageImpl<Airport>(airports, pageable, airportCount));
});
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2022 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.domain;
import org.springframework.data.couchbase.repository.Collection;
/**
* AirportRepository with collection annotation
*
* @author Michael Reiche
*/
@Collection("my_collection2")
public interface ReactiveAirportRepositoryAnnotated extends ReactiveAirportRepository {}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2021 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.domain;
import reactor.core.publisher.Mono;
import org.springframework.data.domain.ReactiveAuditorAware;
/**
* This class returns a string that represents the current user
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
public static final String AUDITOR = "reactive_auditor";
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just(AUDITOR);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2012-2022 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.domain;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
/**
* @author Michael Reiche
*/
public interface ReactivePersonRepository
extends ReactiveCouchbaseRepository<Person, String>, DynamicProxyable<ReactivePersonRepository> {
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-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.domain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface ReactiveUserColRepository
extends ReactiveCouchbaseRepository<UserCol, String>, DynamicProxyable<ReactiveUserColRepository> {
<S extends UserCol> Mono<S> save(S var1);
Flux<UserCol> findByFirstname(String firstname);
Flux<UserCol> findByFirstnameIn(String... firstnames);
Flux<UserCol> findByFirstnameIn(JsonArray firstnames);
Flux<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
Flux<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
Flux<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
Flux<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
Flux<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2012-2022 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.domain;
import reactor.core.publisher.Flux;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ReactiveUserRepository extends ReactiveCouchbaseRepository<User, String> {
Flux<User> findByFirstname(String firstname);
Flux<User> findByFirstnameAndLastname(String firstname, String lastname);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 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.domain;
/**
* Submission entity for tests
*
* @author Michael Reiche
*/
public class Submission extends ComparableEntity {
private final String id;
private final String userId;
private final String talkId;
private final String status;
private final long number;
public Submission(String id, String userId, String talkId, String status, long number) {
this.id = id;
this.userId = userId;
this.talkId = talkId;
this.status = status;
this.number = number;
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 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.domain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
/**
* SubscriptionTokenEntity for tests
*
* @author Michael Reiche
*/
@Getter
@ToString
@EqualsAndHashCode
@Document
public class SubscriptionToken {
private @Id
@GeneratedValue(strategy = GenerationStrategy.UNIQUE)
String id;
private @Version
long version;
private @Field
String subscriptionType;
private @Field
String userName;
private @Field
String appId;
private @Field
String deviceId;
private @Field
long subscriptionDate;
public SubscriptionToken(
String id,
long version,
String subscriptionType,
String userName,
String appId,
String deviceId,
long subscriptionDate) {
this.id = id;
this.version = version;
this.subscriptionType = subscriptionType;
this.userName = userName;
this.appId = appId;
this.deviceId = deviceId;
this.subscriptionDate = subscriptionDate;
}
public void setType(String type) {
type = type;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2020-2022 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.domain;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.stereotype.Repository;
/**
* SubscriptionToken Repository for tests
*
* @author Michael Reiche
*/
@Repository
public interface SubscriptionTokenRepository extends CouchbaseRepository<SubscriptionToken, String> {}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012-2022 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.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import com.couchbase.client.java.encryption.annotation.Encrypted;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* UserEncrypted entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
public class TestEncrypted implements Serializable {
public String id;
@Encrypted
public byte[] encString={1,2,3,4};
public TestEncrypted() {
}
public TestEncrypted(final String id) {
this();
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String toString(){
StringBuffer sb=new StringBuffer();
sb.append("encString: "+encToString());
return sb.toString();
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public void initSimpleTypes(){
}
@Override public boolean equals(Object o){
if(o == null || o.getClass() != getClass()){
return false;
}
TestEncrypted other = (TestEncrypted) o;
//return this.encString == other.encString;
if(other.encString == null && this.encString != null)
return false;
return other.encString.equals(this.encString);
}
public String encToString(){
StringBuffer sb = new StringBuffer();
for(byte c:encString){
if(!sb.isEmpty())
sb.append(",");
sb.append(c);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.mapping.Alias;
import org.springframework.data.util.TypeInformation;
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
@Override
public Alias createAliasFor(TypeInformation<?> type) {
TypeAlias[] typeAlias = type.getType().getAnnotationsByType(TypeAlias.class);
if (typeAlias.length == 1) {
return Alias.of(typeAlias[0].value());
}
return super.createAliasFor(type);
}
}

View File

@@ -0,0 +1,31 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.couchbase.core.convert.CouchbaseTypeMapper;
import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMapper;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.mapping.Alias;
import org.springframework.data.util.TypeInformation;
import java.util.Collections;
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
private final String typeKey;
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
Collections.singletonList(new TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
@Override
public String getTypeKey() {
return typeKey;
}
@Override
public Alias getTypeAlias(TypeInformation<?> info) {
return getAliasFor(info);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2012-2022 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.domain;
import java.io.Serializable;
import java.util.Objects;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER)
public class User extends AbstractUser implements Serializable {
@PersistenceConstructor
public User(final String id, final String firstname, final String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.subtype = AbstractingTypeMapper.Type.USER;
}
@Version protected long version;
@Transient protected String transientInfo;
@CreatedBy protected String createdBy;
@CreatedDate protected long createdDate;
@LastModifiedBy protected String lastModifiedBy;
@LastModifiedDate protected long lastModifiedDate;
public String getLastname() {
return lastname;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public long getLastModifiedDate() {
return lastModifiedDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public int hashCode() {
return Objects.hash(getId(), firstname, lastname);
}
public String getTransientInfo() {
return transientInfo;
}
public void setTransientInfo(String something) {
transientInfo = something;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 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.domain;
import java.util.Objects;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* Annoted User entity for tests
*
* @author Michael Reiche
*/
@Document(expiry = 1)
public class UserAnnotated extends User {
public UserAnnotated(String id, String firstname, String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 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.domain;
import java.util.concurrent.TimeUnit;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* Annotated User entity for tests
*
* @author Michael Reiche
*/
@Document(expiryExpression = "${myExpiryExpression}", expiryUnit = TimeUnit.SECONDS)
public class UserAnnotated2 extends User {
static {
System.setProperty("myExpiryExpression", "2");
}
public UserAnnotated2(String id, String firstname, String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 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.domain;
import java.util.concurrent.TimeUnit;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* Annotated User entity for tests
*
* @author Michael Reiche
*/
@Document(expiry=1, expiryUnit = TimeUnit.SECONDS)
public class UserAnnotated3 extends User {
public UserAnnotated3(String id, String firstname, String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-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.domain;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Scope;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
@Scope("other_scope")
@Collection("other_collection")
public class UserCol extends User {
@PersistenceConstructor
public UserCol(final String id, final String firstname, final String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserColRepository extends CouchbaseRepository<UserCol, String>, DynamicProxyable<UserColRepository> {
// CouchbaseRepositoryQueryCollectionIntegrationTests.testScopeCollectionAnnotationSwap() relies on this
// being commented out.
// <S extends UserCol> S save(S var1);
List<UserCol> findByFirstname(String firstname);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
UserCol getById(String id);
List<UserCol> findByFirstnameIn(String... firstnames);
List<UserCol> findByFirstnameIn(JsonArray firstnames);
List<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright 2012-2022 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.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import com.couchbase.client.java.encryption.annotation.Encrypted;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* UserEncrypted entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER)
public class UserEncrypted extends AbstractUser implements Serializable {
public UserEncrypted() {
this._class = "abstractuser";
this.subtype = AbstractingTypeMapper.Type.USER;
}
public String _class; // cheat a little so that will work with Java SDK
@PersistenceConstructor
public UserEncrypted(final String id, final String firstname, final String lastname) {
this();
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
public UserEncrypted(final String id, final String firstname, final String lastname, final String encryptedField) {
this();
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.encryptedField = encryptedField;
}
static DateTime NOW_DateTime = DateTime.now(DateTimeZone.UTC);
static Date NOW_Date = Date.from(Instant.now());
@Version protected long version;
@Encrypted(migration = Encrypted.Migration.FROM_UNENCRYPTED) public String encryptedField;
@Encrypted public boolean encboolean;
@Encrypted public boolean[] encbooleans;
@Encrypted public Boolean encBoolean;
@Encrypted public Boolean[] encBooleans;
@Encrypted public long enclong;
@Encrypted public long[] enclongs;
@Encrypted public Long encLong;
@Encrypted public Long[] encLongs;
@Encrypted public short encshort;
@Encrypted public short[] encshorts;
@Encrypted public Short encShort;
@Encrypted public Short[] encShorts;
@Encrypted public int encinteger;
@Encrypted public int[] encintegers;
@Encrypted public Integer encInteger;
@Encrypted public Integer[] encIntegers;
@Encrypted public byte encbyte;
public byte[] plainbytes;
@Encrypted public byte[] encbytes;
@Encrypted public Byte encByte;
@Encrypted public Byte[] encBytes;
@Encrypted public float encfloat;
@Encrypted public float[] encfloats;
@Encrypted public Float encFloat;
@Encrypted public Float[] encFloats;
@Encrypted public double encdouble;
@Encrypted public double[] encdoubles;
@Encrypted public Double encDouble;
@Encrypted public Double[] encDoubles;
@Encrypted public char encchar='x'; // need to initialize as char(0) is not legal
@Encrypted public char[] encchars;
@Encrypted public Character encCharacter;
@Encrypted public Character[] encCharacters;
@Encrypted public String encString;
@Encrypted public String[] encStrings;
@Encrypted public Date encDate;
@Encrypted public Date[] encDates;
@Encrypted public Locale encLocal;
@Encrypted public Locale[] encLocales;
@Encrypted public QueryScanConsistency encEnum;
@Encrypted public QueryScanConsistency[] encEnums;
@Encrypted public Class<?> clazz;
@Encrypted public BigInteger encBigInteger;
@Encrypted public BigDecimal encBigDecimal;
@Encrypted public UUID encUUID;
@Encrypted public DateTime encDateTime;
@Encrypted public Address encAddress = new Address();
public Date plainDate;
public DateTime plainDateTime;
public List nicknames;
public Address homeAddress = null;
public List<AddressWithEncStreet> addresses = new ArrayList<>();
public String getLastname() {
return lastname;
}
public long getVersion() {
return version;
}
public void setId(String id) {
this.id = id;
}
public void setVersion(long version) {
this.version = version;
}
public void setHomeAddress(Address address) {
this.homeAddress = address;
}
public void setEncAddress(Address address) {
this.encAddress = address;
}
public void addAddress(AddressWithEncStreet address) {
this.addresses.add(address);
}
public UserEncrypted withClass(String _class) {
this._class = _class;
return this;
}
@Override
public int hashCode() {
return Objects.hash(getId(), firstname, lastname);
}
public void initSimpleTypes() {
encboolean = false;
encbooleans = new boolean[] { true, false };
encBoolean = true;
encBooleans = new Boolean[] { true, false };
enclong = 1;
enclongs = new long[] { 1, 2 };
encLong = Long.valueOf(1);
encLongs = new Long[] { Long.valueOf(1), Long.valueOf(2) };
encshort = 1;
encshorts = new short[] { 3, 4 };
encShort = 5;
encShorts = new Short[] { 6, 7 };
encinteger = 1;
encintegers = new int[] { 2, 3 };
encInteger = 4;
encIntegers = new Integer[] { 5, 6 };
encbyte = 32;
encbytes = new byte[] { 1, 2, 3, 4 };
plainbytes = new byte[] { 1, 2, 3, 4 };
encByte = 48;
encBytes = new Byte[] { 4, 5, 6, 7 };
encfloat = 1;
encfloats = new float[] { 1, 2 };
encFloat = Float.valueOf("1.1");
encFloats = new Float[] { encFloat };
encdouble = 1.2;
encdoubles = new double[] { 3.4, 5.6 };
encDouble = 7.8;
encDoubles = new Double[] { 9.10, 11.12 };
encchar = 'a';
encchars = new char[] { 'b', 'c', 'd' };
encCharacter = 'a';
encCharacters = new Character[] { 'a', 'b' };
encString = "myString";
encStrings = new String[] { "myString" };
encDate = NOW_Date;
encDates = new Date[] { NOW_Date };
encLocal = Locale.US;
encLocales = new Locale[] { Locale.US };
encEnum = QueryScanConsistency.NOT_BOUNDED;
encEnums = new QueryScanConsistency[] { QueryScanConsistency.NOT_BOUNDED };
encBigInteger = new BigInteger("123");
encBigDecimal = new BigDecimal("456");
encUUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
//clazz = String.class;
encDateTime = NOW_DateTime;
encAddress = new Address();
plainDate = NOW_Date;
plainDateTime = NOW_DateTime;
nicknames = List.of("Happy", "Sleepy");
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserEncryptedRepository extends CouchbaseRepository<UserEncrypted, String> {
List<User> findByFirstname(String firstname);
List<User> findByFirstnameIgnoreCase(String firstname);
Stream<User> findByLastname(String lastname);
List<User> findByFirstnameIn(String... firstnames);
List<User> findByFirstnameIn(JsonArray firstnames);
List<User> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<User> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
@Query("#{#n1ql.selectEntity}|#{#n1ql.filter}|#{#n1ql.bucket}|#{#n1ql.scope}|#{#n1ql.collection}")
@Scope("thisScope")
@Collection("thisCollection")
List<User> spelTests();
// simulate a slow operation
@Cacheable("mySpringCache")
default List<User> getByFirstname(String firstname) {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException ie) {}
return findByFirstname(firstname);
}
@Override
UserEncrypted save(UserEncrypted user);
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2012-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.domain;
import java.util.Objects;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
public class UserJustLastName extends ComparableEntity {
@Id private String id;
private String lastname;
public User user;
@PersistenceConstructor
public UserJustLastName(final String id, final String lastname) {
this.id = id;
this.lastname = lastname;
this.user = new User("1", "first", "last");
}
public String getId() {
return id;
}
public String getLastname() {
return lastname;
}
@Override
public int hashCode() {
return Objects.hash(id, lastname);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2022 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.domain;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByFirstname(String firstname);
List<User> findByFirstnameIgnoreCase(String firstname);
Stream<User> findByLastname(String lastname);
List<User> findByFirstnameIn(String... firstnames);
List<User> findByFirstnameIn(JsonArray firstnames);
List<User> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<User> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
@Query("#{#n1ql.selectEntity}|#{#n1ql.filter}|#{#n1ql.bucket}|#{#n1ql.scope}|#{#n1ql.collection}")
@Scope("thisScope")
@Collection("thisCollection")
List<User> spelTests();
// simulate a slow operation
@Cacheable("mySpringCache")
default List<User> getByFirstname(String firstname) {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException ie) {}
return findByFirstname(firstname);
}
@Override
User save(User user);
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020-2021 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.domain;
import lombok.Data;
import java.util.List;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.index.CompositeQueryIndex;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.query.FetchType;
import org.springframework.data.couchbase.core.query.N1qlJoin;
/**
* UserSubmission entity for tests
*
* @author Michael Reiche
*/
@Data
@Document
@TypeAlias("user")
@CompositeQueryIndex(fields = { "id", "username", "email" })
public class UserSubmission extends ComparableEntity {
private String id;
private String username;
private String email;
private String password;
private List<String> roles;
private Address address;
@N1qlJoin(on = "meta(lks).id=rks.parentId", fetchType = FetchType.IMMEDIATE) List<Address> otherAddresses;
private int credits;
private List<Submission> submissions;
private List<Course> courses;
public void setSubmissions(List<Submission> submissions) {
this.submissions = submissions;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2020-2022 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.domain;
import lombok.Data;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.query.FetchType;
import org.springframework.data.couchbase.core.query.N1qlJoin;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Scope;
import java.util.List;
/**
* UserSubmissionAnnotated entity for tests
*
* @author Michael Reiche
*/
@Data
@Document
@TypeAlias("user")
@Scope("my_scope")
@Collection("my_collection")
public class UserSubmissionAnnotated extends ComparableEntity {
private String id;
private String username;
private String email;
private String password;
private List<String> roles;
@N1qlJoin(on = "meta(lks).id=rks.parentId", fetchType = FetchType.IMMEDIATE) List<AddressAnnotated> otherAddresses;
private Address address;
private int credits;
private List<Submission> submissions;
private List<Course> courses;
public void setSubmissions(List<Submission> submissions) {
this.submissions = submissions;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* UserSubmissionAnnotatedRepository for tests
*
* @author Michael Reiche
*/
@Repository
public interface UserSubmissionAnnotatedRepository extends CouchbaseRepository<UserSubmissionAnnotated, String> {
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<UserSubmissionAnnotated> findByUsername(String username);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 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.domain;
import lombok.Data;
import java.util.List;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.index.CompositeQueryIndex;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* UserSubmission entity for tests
*
* @author Michael Reiche
*/
@Data
@Document
@TypeAlias("user")
@CompositeQueryIndex(fields = { "id", "username", "email" })
public class UserSubmissionProjected extends ComparableEntity {
private String id;
private String username;
private List<String> roles;
private Address address;
private List<Course> courses;
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020-2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* UserSubmission Repository for tests
*
* @author Michael Reiche
*/
@Repository
public interface UserSubmissionRepository extends CouchbaseRepository<UserSubmission, String> {
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<UserSubmission> findByUsername(String username);
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020-2022 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.domain;
import lombok.Data;
import java.util.List;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.query.FetchType;
import org.springframework.data.couchbase.core.query.N1qlJoin;
import org.springframework.data.couchbase.repository.Collection;
/**
* UserSubmissionAnnotated entity for tests
*
* @author Michael Reiche
*/
@Data
@Document
// there is no @Scope annotation on this entity
@Collection("my_collection")
@TypeAlias("user")
public class UserSubmissionUnannotated extends ComparableEntity {
private String id;
private String username;
private String email;
private String password;
private List<String> roles;
@N1qlJoin(on = "meta(lks).id=rks.parentId", fetchType = FetchType.IMMEDIATE) List<AddressAnnotated> otherAddresses;
private Address address;
private int credits;
private List<Submission> submissions;
private List<Course> courses;
public void setSubmissions(List<Submission> submissions) {
this.submissions = submissions;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020-2022 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.domain;
import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* UserSubmissionAnnotatedRepository for tests
*
* @author Michael Reiche
*/
@Repository
public interface UserSubmissionUnannotatedRepository extends CouchbaseRepository<UserSubmissionUnannotated, String> {
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<UserSubmissionUnannotated> findByUsername(String username);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-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.domain.time;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;
import org.springframework.data.auditing.DateTimeProvider;
public class AuditingDateTimeProvider implements DateTimeProvider {
private DateTimeService dateTimeService = new FixedDateTimeService();
public AuditingDateTimeProvider() {}
public AuditingDateTimeProvider(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
@Override
public Optional<TemporalAccessor> getNow() {
return Optional.of(Instant.ofEpochSecond(dateTimeService.getCurrentDateAndTime().toEpochSecond()));
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2012-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.domain.time;
import java.time.ZonedDateTime;
public class CurrentDateTimeService implements DateTimeService {
@Override
public ZonedDateTime getCurrentDateAndTime() {
return ZonedDateTime.now();
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2012-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.domain.time;
import java.time.ZonedDateTime;
public interface DateTimeService {
ZonedDateTime getCurrentDateAndTime();
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-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.domain.time;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class FixedDateTimeService implements DateTimeService {
public static void main(String[] args) {
System.out.println((new FixedDateTimeService()).getCurrentDateAndTime());
}
@Override
public ZonedDateTime getCurrentDateAndTime() {
return ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("GMT-8"));
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2017-2022 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.jupiter.api.Assertions.assertEquals;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.domain.AbstractUser;
import org.springframework.data.couchbase.domain.AbstractUserRepository;
import org.springframework.data.couchbase.domain.AbstractingMappingCouchbaseConverter;
import org.springframework.data.couchbase.domain.OtherUser;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
/**
* Abstract Repository tests
*
* @author Michael Reiche
*/
@SpringJUnitConfig(CouchbaseAbstractRepositoryIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class CouchbaseAbstractRepositoryIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired AbstractUserRepository abstractUserRepository;
@Test
void saveAndFindAbstract() {
// User extends AbstractUser
// OtherUser extends Abstractuser
{
User concreteUser = null;
{
concreteUser = new User(UUID.randomUUID().toString(), "userFirstname", "userLastname");
assertEquals(User.class, concreteUser.getClass());
concreteUser = abstractUserRepository.save(concreteUser); // this will now have version set
// Queries on repositories for abstract entities must be @Query and not include
// #{#n1ql.filter} (i.e. _class = <classname> ) as the classname will not match any document
AbstractUser found = abstractUserRepository.myFindById(concreteUser.getId());
assertEquals(concreteUser, found);
assertEquals(concreteUser.getClass(), found.getClass());
}
{
Optional<AbstractUser> found = abstractUserRepository.findById(concreteUser.getId());
assertEquals(concreteUser, found.get());
}
{
List<AbstractUser> found = abstractUserRepository.findByFirstname(concreteUser.getFirstname());
assertEquals(1, found.size(), "should have found one user");
assertEquals(concreteUser, found.get(0));
}
abstractUserRepository.delete(concreteUser);
}
{
AbstractUser abstractUser = new OtherUser(UUID.randomUUID().toString(), "userFirstname", "userLastname");
assertEquals(OtherUser.class, abstractUser.getClass());
abstractUserRepository.save(abstractUser);
{
// not going to find this one as using the type _class = AbstractUser ???
AbstractUser found = abstractUserRepository.myFindById(abstractUser.getId());
assertEquals(abstractUser, found);
assertEquals(abstractUser.getClass(), found.getClass());
}
{
Optional<AbstractUser> found = abstractUserRepository.findById(abstractUser.getId());
assertEquals(abstractUser, found.get());
}
{
List<AbstractUser> found = abstractUserRepository.findByFirstname(abstractUser.getFirstname());
assertEquals(1, found.size(), "should have found one user");
assertEquals(abstractUser, found.get(0));
}
abstractUserRepository.delete(abstractUser);
}
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
/**
* This uses a CustomMappingCouchbaseConverter instead of MappingCouchbaseConverter, which in turn uses
* AbstractTypeMapper which has special mapping for AbstractUser
*/
@Override
@Bean(name = "mappingCouchbaseConverter")
public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext,
CouchbaseCustomConversions couchbaseCustomConversions /* there is a customConversions() method bean */) {
// MappingCouchbaseConverter relies on a SimpleInformationMapper
// that has an getAliasFor(info) that just returns getType().getName().
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
// use the DocumentType annotation
MappingCouchbaseConverter converter = new AbstractingMappingCouchbaseConverter(couchbaseMappingContext,
typeKey());
converter.setCustomConversions(couchbaseCustomConversions);
return converter;
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2017-2022 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.jupiter.api.Assertions.*;
import java.util.Optional;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
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.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.manager.query.QueryIndex;
@SpringJUnitConfig(CouchbaseRepositoryAutoQueryIndexIntegrationTests.Config.class)
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryAutoQueryIndexIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired private Cluster cluster;
/**
* Since the index creation happens at startup, the only way to properly check is by querying the index list and
* making sure it is present.
*/
@Test
void createsSingleFieldIndex() {
// This failed once against Capella. Not sure why.
Optional<QueryIndex> foundIndex = cluster.queryIndexes().getAllIndexes(bucketName()).stream()
.filter(i -> i.name().equals("idx_airline_name")).findFirst();
assertTrue(foundIndex.isPresent());
assertTrue(foundIndex.get().condition().get().contains("_class"));
}
@Test
void createsCompositeIndex() {
Optional<QueryIndex> foundIndex = cluster.queryIndexes().getAllIndexes(bucketName()).stream()
.filter(i -> i.name().equals("idx_airline_id_name")).findFirst();
assertTrue(foundIndex.isPresent());
assertTrue(foundIndex.get().condition().get().contains("_class"));
}
@Test
void createsCompositeIndexWithPath() {
Optional<QueryIndex> foundIndex = cluster.queryIndexes().getAllIndexes(bucketName()).stream()
.filter(i -> i.name().equals("idx_airline_id_something_name")).findFirst();
assertTrue(foundIndex.isPresent());
assertTrue(foundIndex.get().condition().get().contains("_class"));
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if(config().isUsingCloud()) {
builder.securityConfig(SecurityConfig.builder()
.trustManagerFactory(InsecureTrustManagerFactory.INSTANCE)
.enableTls(true));
}
}
@Override
protected boolean autoIndexCreation() {
return true;
}
}
}

View File

@@ -0,0 +1,417 @@
/*
* Copyright 2012-2022 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 com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataRetrievalFailureException;
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.domain.Address;
import org.springframework.data.couchbase.domain.AddressWithEncStreet;
import org.springframework.data.couchbase.domain.TestEncrypted;
import org.springframework.data.couchbase.domain.UserEncrypted;
import org.springframework.data.couchbase.domain.UserEncryptedRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.encryption.CryptoManager;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.encryption.AeadAes256CbcHmacSha512Provider;
import com.couchbase.client.encryption.DefaultCryptoManager;
import com.couchbase.client.encryption.Keyring;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
/**
* Repository KV tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@SpringJUnitConfig(CouchbaseRepositoryFieldLevelEncryptionIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryFieldLevelEncryptionIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired UserEncryptedRepository userEncryptedRepository;
@Autowired CouchbaseClientFactory clientFactory;
@Autowired CouchbaseTemplate couchbaseTemplate;
@BeforeEach
public void beforeEach() {
super.beforeEach();
List<UserEncrypted> users = couchbaseTemplate.findByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
for (UserEncrypted user : users) {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
try { // may have also used upperCased-id
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
couchbaseTemplate.removeByQuery(UserEncrypted.class).all();
couchbaseTemplate.findByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
}
@Test
void javaSDKEncryption() {
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindByTestId() {
TestEncrypted user = new TestEncrypted(UUID.randomUUID().toString());
user.initSimpleTypes();
couchbaseTemplate.save(user);
TestEncrypted writeSpringReadSpring = couchbaseTemplate.findById(TestEncrypted.class).one(user.id);
System.err.println(user);
System.err.println(writeSpringReadSpring);
assertEquals(user.toString(), writeSpringReadSpring.toString());
TestEncrypted writeSpringReadSDK = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.id).contentAs(TestEncrypted.class);
writeSpringReadSDK.setId(user.id);
assertEquals(user.toString(), writeSpringReadSDK.toString());
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId().toUpperCase(),
user);
TestEncrypted writeSDKReadSDK = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.getId().toUpperCase()).contentAs(TestEncrypted.class);
writeSDKReadSDK.setId(user.getId());
assertEquals(user.toString(), writeSDKReadSDK.toString());
TestEncrypted writeSDKReadSpring = couchbaseTemplate.findById(TestEncrypted.class).one(user.getId().toUpperCase());
writeSDKReadSpring.setId(user.getId());
assertEquals(user.toString(), writeSDKReadSpring.toString());
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void writeSpring_readSpring() {
boolean cleanAfter = false;
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "writeSpring_readSpring", "l", "hello");
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setEncStreet("Olcott Street");
address.setCity("Santa Clara");
user.addAddress(address);
user.setHomeAddress(null);
Address encAddress = new Address(); // encrypted address with plaintext street.
encAddress.setStreet("Castro St");
encAddress.setCity("Mountain View");
user.setEncAddress(encAddress);
user.initSimpleTypes();
// save the user with spring
assertFalse(userEncryptedRepository.existsById(user.getId()));
userEncryptedRepository.save(user);
// read the user with Spring
Optional<UserEncrypted> writeSpringReadSpring = userEncryptedRepository.findById(user.getId());
assertTrue(writeSpringReadSpring.isPresent());
writeSpringReadSpring.ifPresent(u -> assertEquals(user, u));
if (cleanAfter) {
try {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void writeSpring_readSDK() {
boolean cleanAfter = false;
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "writeSpring_readSDK", "l", "hello");
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setEncStreet("Olcott Street");
address.setCity("Santa Clara");
user.addAddress(address);
user.setHomeAddress(null);
Address encAddress = new Address(); // encrypted address with plaintext street.
encAddress.setStreet("Castro St");
encAddress.setCity("Mountain View");
user.setEncAddress(encAddress);
user.initSimpleTypes();
// save the user with spring
assertFalse(userEncryptedRepository.existsById(user.getId()));
userEncryptedRepository.save(user);
// read user with SDK
UserEncrypted writeSpringReadSDK = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.getId()).contentAs(UserEncrypted.class);
writeSpringReadSDK.setId(user.getId());
writeSpringReadSDK.setVersion(user.getVersion());
assertEquals(user, writeSpringReadSDK);
if (cleanAfter) {
try {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void writeSDK_readSpring() {
boolean cleanAfter = false;
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "writeSDK_readSpring", "l", "hello");
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setEncStreet("Olcott Street");
address.setCity("Santa Clara");
user.addAddress(address);
user.setHomeAddress(null);
Address encAddress = new Address(); // encrypted address with plaintext street.
encAddress.setStreet("Castro St");
encAddress.setCity("Mountain View");
user.setEncAddress(encAddress);
user.initSimpleTypes();
// save the user with the SDK
assertFalse(userEncryptedRepository.existsById(user.getId().toUpperCase()));
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId().toUpperCase(),
user);
Optional<UserEncrypted> writeSDKReadSpring = userEncryptedRepository.findById(user.getId().toUpperCase());
assertTrue(writeSDKReadSpring.isPresent());
writeSDKReadSpring.get().setId(user.getId());
writeSDKReadSpring.get().setVersion(user.getVersion());
writeSDKReadSpring.ifPresent(u -> assertEquals(user, u));
if (cleanAfter) {
try {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void writeSDK_readSDK() {
boolean cleanAfter = false;
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "writeSDK_readSDK", "l", "hello");
AddressWithEncStreet address = new AddressWithEncStreet(); // plaintext address with encrypted street
address.setEncStreet("Olcott Street");
address.setCity("Santa Clara");
user.addAddress(address);
user.setHomeAddress(null);
Address encAddress = new Address(); // encrypted address with plaintext street.
encAddress.setStreet("Castro St");
encAddress.setCity("Mountain View");
user.setEncAddress(encAddress);
user.clazz = String.class; // not supported by SDK, but not support by UserEncrypted.toString() either.
user.initSimpleTypes();
// write the user with the SDK
assertFalse(userEncryptedRepository.existsById(user.getId().toUpperCase()));
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId().toUpperCase(),
user);
// read the user with the SDK
UserEncrypted writeSDKReadSDK = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.getId().toUpperCase()).contentAs(UserEncrypted.class);
writeSDKReadSDK.setId(user.getId());
writeSDKReadSDK.setVersion(user.getVersion());
assertEquals(user.clazz, writeSDKReadSDK.clazz);
writeSDKReadSDK.clazz = null; // null these out as UserEncrypted.toString() doesn't support them.
user.clazz = null;
assertEquals(user, writeSDKReadSDK);
if (cleanAfter) {
try {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void testFromMigration() {
boolean cleanAfter = true;
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "testFromMigration", "l",
"migrating from unencrypted");
JsonObject jo = JsonObject.jo();
jo.put("firstname", user.getFirstname());
jo.put("lastname", user.getLastname());
jo.put("encryptedField", user.encryptedField);
jo.put("_class", user._class);
// save it unencrypted
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId(), jo);
JsonObject migration = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.getId()).contentAsObject();
assertEquals("migrating from unencrypted", migration.get("encryptedField"));
assertNull(migration.get(CryptoManager.DEFAULT_ENCRYPTER_ALIAS + "encryptedField"));
// it will be retrieved successfully
Optional<UserEncrypted> found = userEncryptedRepository.findById(user.getId());
assertTrue(found.isPresent());
user.setVersion(found.get().getVersion());
found.ifPresent(u -> assertEquals(user, u));
// save it encrypted
UserEncrypted saved = userEncryptedRepository.save(user);
// it will be retrieved successfully
Optional<UserEncrypted> foundEnc = userEncryptedRepository.findById(user.getId());
assertTrue(foundEnc.isPresent());
user.setVersion(foundEnc.get().getVersion());
foundEnc.ifPresent(u -> assertEquals(user, u));
// retrieve it without decrypting
JsonObject encrypted = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
.get(user.getId()).contentAsObject();
assertEquals("myKey",
((JsonObject) encrypted.get(CryptoManager.DEFAULT_ENCRYPTED_FIELD_NAME_PREFIX + "encryptedField")).get("kid"));
assertNull(encrypted.get("encryptedField"));
if (cleanAfter) {
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
try { // may have also used upperCased-id
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
} catch (DataRetrievalFailureException iae) {
// ignore
}
}
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
public ObjectMapper couchbaseObjectMapper(CryptoManager cryptoManager) {
ObjectMapper om = super.couchbaseObjectMapper(cryptoManager);
om.registerModule(new JodaModule());
return om;
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
CryptoManager cryptoManager = cryptoManager();
builder.cryptoManager(cryptoManager).build();
}
@Override
protected CryptoManager cryptoManager() {
Map<String, byte[]> keyMap = new HashMap();
keyMap.put("myKey", new byte[64] /* all zeroes */);
Keyring keyring = Keyring.fromMap(keyMap);
AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring)
/*.securityProvider(secProvider)*/.build();
return new WrappingCryptoManager(DefaultCryptoManager.builder().decrypter(provider.decrypter())
.defaultEncrypter(provider.encrypterForKey("myKey")).build());
}
public class WrappingCryptoManager implements CryptoManager {
CryptoManager cryptoManager;
public WrappingCryptoManager(CryptoManager cryptoManager) {
this.cryptoManager = cryptoManager;
}
@Override
public Map<String, Object> encrypt(byte[] plaintext, String encrypterAlias) {
Map<String, Object> encryptedNode = cryptoManager.encrypt(plaintext, encrypterAlias);
return encryptedNode;
}
@Override
public byte[] decrypt(Map<String, Object> encryptedNode) {
byte[] result = cryptoManager.decrypt(encryptedNode);
return result;
}
private String toBytes(byte[] plaintext) {
StringBuffer sb = new StringBuffer();
for (byte b : plaintext) {
sb.append(b);
sb.append(" ");
}
return sb.toString();
}
private boolean cmp(byte[] a, byte[] b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i])
return false;
}
return true;
}
byte[] canned_sdk_encbytes = { 34, 65, 81, 73, 68, 66, 65, 61, 61, 34 };
byte[] canned_spring_encbytes = { 91, 49, 44, 50, 44, 51, 44, 52, 93 };
}
}
}

View File

@@ -0,0 +1,234 @@
/*
* Copyright 2012-2022 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 com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.Library;
import org.springframework.data.couchbase.domain.LibraryRepository;
import org.springframework.data.couchbase.domain.PersonValue;
import org.springframework.data.couchbase.domain.PersonValueRepository;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.SubscriptionToken;
import org.springframework.data.couchbase.domain.SubscriptionTokenRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.domain.UserSubmissionRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.kv.GetResult;
/**
* Repository KV tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@SpringJUnitConfig(CouchbaseRepositoryKeyValueIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired UserRepository userRepository;
@Autowired LibraryRepository libraryRepository;
@Autowired SubscriptionTokenRepository subscriptionTokenRepository;
@Autowired UserSubmissionRepository userSubmissionRepository;
@Autowired AirlineRepository airlineRepository;
@Autowired PersonValueRepository personValueRepository;
@Autowired CouchbaseTemplate couchbaseTemplate;
@BeforeEach
public void beforeEach() {
super.beforeEach();
couchbaseTemplate.removeByQuery(SubscriptionToken.class).withConsistency(REQUEST_PLUS).all();
couchbaseTemplate.findByQuery(SubscriptionToken.class).withConsistency(REQUEST_PLUS).all();
}
@Test
void subscriptionToken() {
SubscriptionToken st = new SubscriptionToken("id", 0, "type", "Dave Smith", "app123", "dev123", 0);
st = subscriptionTokenRepository.save(st);
st = subscriptionTokenRepository.findById(st.getId()).get();
GetResult jdkResult = couchbaseTemplate.getCouchbaseClientFactory().getDefaultCollection().get(st.getId());
assertNotEquals(0, st.getVersion());
assertEquals(jdkResult.cas(), st.getVersion());
subscriptionTokenRepository.delete(st);
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveReplaceUpsertInsert() {
// the User class has a version.
User user = new User(UUID.randomUUID().toString(), "f", "l");
// save the document - we don't care how on this call
userRepository.save(user);
// Now set the version to 0, it should attempt an insert and fail.
long saveVersion = user.getVersion();
user.setVersion(0);
assertThrows(DuplicateKeyException.class, () -> userRepository.save(user));
user.setVersion(saveVersion + 1);
assertThrows(OptimisticLockingFailureException.class, () -> userRepository.save(user));
userRepository.delete(user);
// Airline does not have a version
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline", null);
// save the document - we don't care how on this call
airlineRepository.save(airline);
airlineRepository.save(airline); // If it was an insert it would fail. Can't tell if it is an upsert or replace.
airlineRepository.delete(airline);
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindById() {
User user = new User(UUID.randomUUID().toString(), "saveAndFindById", "l");
// this currently fails when using mocked in integration.properties with status "UNKNOWN"
assertFalse(userRepository.existsById(user.getId()));
userRepository.save(user);
Optional<User> found = userRepository.findById(user.getId());
assertTrue(found.isPresent());
found.ifPresent(u -> assertEquals(user, u));
assertTrue(userRepository.existsById(user.getId()));
userRepository.delete(user);
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindImmutableById() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
PersonValue personValue = new PersonValue(null, 0, "saveAndFindImmutableById", "l");
personValue = personValueRepository.save(personValue);
Optional<PersonValue> found = personValueRepository.findById(personValue.getId());
assertTrue(found.isPresent());
assertEquals(personValue, found.get());
personValueRepository.delete(personValue);
}
@Test // DATACOUCH-564
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindByIdWithList() {
List<String> books = new ArrayList<>();
books.add("book1");
books.add("book2");
Library library = new Library(UUID.randomUUID().toString(), books);
// this currently fails when using mocked in integration.properties with status "UNKNOWN"
assertFalse(libraryRepository.existsById(library.getId()));
libraryRepository.save(library);
Optional<Library> found = libraryRepository.findById(library.getId());
assertTrue(found.isPresent());
found.ifPresent(l -> assertEquals(library, l));
assertTrue(userRepository.existsById(library.getId()));
libraryRepository.delete(library);
assertFalse(userRepository.existsById(library.getId()));
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindByWithNestedId() {
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
user.setCourses(Arrays.asList(new Course(UUID.randomUUID().toString(), user.getId(), "581")));
// this currently fails when using mocked in integration.properties with status "UNKNOWN"
assertFalse(userSubmissionRepository.existsById(user.getId()));
userSubmissionRepository.save(user);
Optional<UserSubmission> found = userSubmissionRepository.findById(user.getId());
assertTrue(found.isPresent());
found.ifPresent(u -> assertEquals(user, u));
assertTrue(userSubmissionRepository.existsById(user.getId()));
assertEquals(user.getSubmissions().get(0).getId(), found.get().getSubmissions().get(0).getId());
assertEquals(user.getCourses().get(0).getId(), found.get().getCourses().get(0).getId());
assertEquals(user, found.get());
userSubmissionRepository.delete(user);
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2012-2022 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.ReactiveAirlineRepository;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
import org.springframework.data.couchbase.domain.ReactiveNaiveAuditorAware;
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
/**
* @author Michael Reiche
*/
@SpringJUnitConfig(ReactiveCouchbaseRepositoryKeyValueIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired ReactiveUserRepository userRepository;
@Autowired ReactiveAirportRepository reactiveAirportRepository;
@Autowired ReactiveAirlineRepository reactiveAirlineRepository;
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveReplaceUpsertInsert() {
// the User class has a version.
User user = new User(UUID.randomUUID().toString(), "f", "l");
// save the document - we don't care how on this call
userRepository.save(user).block();
// Now set the version to 0, it should attempt an insert and fail.
long saveVersion = user.getVersion();
user.setVersion(0);
assertThrows(DuplicateKeyException.class, () -> userRepository.save(user).block());
user.setVersion(saveVersion + 1);
assertThrows(OptimisticLockingFailureException.class, () -> userRepository.save(user).block());
userRepository.delete(user);
// Airline does not have a version
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline", null);
// save the document - we don't care how on this call
reactiveAirlineRepository.save(airline).block();
reactiveAirlineRepository.save(airline).block(); // If it was an insert it would fail. Can't tell if an upsert or
// replace.
reactiveAirlineRepository.delete(airline).block();
}
@Test
void saveAndFindById() {
User user = new User(UUID.randomUUID().toString(), "saveAndFindById_reactive", "l");
assertFalse(userRepository.existsById(user.getId()).block());
final User save = userRepository.save(user).block();
Optional<User> found = userRepository.findById(user.getId()).blockOptional();
assertTrue(found.isPresent());
found.ifPresent(u -> assertEquals(save, u));
assertTrue(userRepository.existsById(user.getId()).block());
userRepository.delete(user).block();
}
@Test
void findByIdAudited() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "low2");
Airport saved = reactiveAirportRepository.save(vie).block();
Airport airport1 = reactiveAirportRepository.findById(saved.getId()).block();
assertEquals(airport1, saved);
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide
// this
} finally {
reactiveAirportRepository.delete(vie).block();
}
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
@EnableReactiveCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Bean(name = "auditorAwareRef")
public ReactiveNaiveAuditorAware testAuditorAware() {
return new ReactiveNaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
}

View File

@@ -0,0 +1,331 @@
/*
* Copyright 2017-2022 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 java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
/**
* template class for Reactive Couchbase operations
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@SpringJUnitConfig(ReactiveCouchbaseRepositoryQueryIntegrationTests.Config.class)
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegrationTests {
@Autowired CouchbaseClientFactory clientFactory;
@Autowired ReactiveAirportRepository reactiveAirportRepository; // intellij flags "Could not Autowire", runs ok.
@Autowired ReactiveUserRepository userRepository; // intellij flags "Could not Autowire", but it runs ok.
@Test
void shouldSaveAndFindAll() {
Airport vie = null;
Airport jfk = null;
try {
vie = new Airport("airports::vie", "vie", "low1");
reactiveAirportRepository.save(vie).block();
jfk = new Airport("airports::jfk", "JFK", "xxxx");
reactiveAirportRepository.save(jfk).block();
List<Airport> all = reactiveAirportRepository.findAll().toStream().collect(Collectors.toList());
assertFalse(all.isEmpty());
assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::vie")));
assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::jfk")));
} finally {
reactiveAirportRepository.delete(vie).block();
reactiveAirportRepository.delete(jfk).block();
}
}
@Test
void testQuery() {
Airport vie = null;
Airport jfk = null;
try {
vie = new Airport("airports::vie", "vie", "low1");
reactiveAirportRepository.save(vie).block();
jfk = new Airport("airports::jfk", "JFK", "xxxx");
reactiveAirportRepository.save(jfk).block();
List<String> all = reactiveAirportRepository.findIdByDynamicN1ql("", "").toStream().collect(Collectors.toList());
System.out.println(all);
assertFalse(all.isEmpty());
assertTrue(all.stream().anyMatch(a -> a.equals("airports::vie")));
assertTrue(all.stream().anyMatch(a -> a.equals("airports::jfk")));
} finally {
reactiveAirportRepository.delete(vie).block();
reactiveAirportRepository.delete(jfk).block();
}
}
@Test
void findBySimpleProperty() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "low2");
reactiveAirportRepository.save(vie).block();
List<Airport> airports1 = reactiveAirportRepository.findAllByIata(Mono.just("vie")).collectList().block();
assertEquals(1, airports1.size());
List<Airport> airports2 = reactiveAirportRepository.findAllByIata(Mono.just("vie")).collectList().block();
assertEquals(1, airports2.size());
vie = reactiveAirportRepository.save(vie).block();
List<Airport> airports = reactiveAirportRepository.findAllByIata(Mono.just("vie")).collectList().block();
assertEquals(1, airports.size());
Airport airport1 = reactiveAirportRepository.findById(airports.get(0).getId()).block();
assertEquals(airport1.getIata(), vie.getIata());
Airport airport2 = reactiveAirportRepository.findByIata(airports.get(0).getIata()).block();
assertEquals(airport1.getId(), vie.getId());
} finally {
reactiveAirportRepository.delete(vie).block();
}
}
@Test
public void testCas() {
User user = new User("1", "Dave", "Wilson");
userRepository.save(user).block();
long saveVersion = user.getVersion();
user.setVersion(user.getVersion() - 1);
assertThrows(OptimisticLockingFailureException.class, () -> userRepository.save(user).block());
user.setVersion(saveVersion);
userRepository.save(user).block();
userRepository.delete(user).block();
}
@Test
void limitTest() {
Airport vie = new Airport("airports::vie", "vie", "low3");
Airport saved1 = reactiveAirportRepository.save(vie).block();
Airport saved2 = reactiveAirportRepository.save(vie.withId(UUID.randomUUID().toString())).block();
try {
reactiveAirportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
Mono<Airport> airport = reactiveAirportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
System.out.println("------------------------------");
System.out.println(airport.block());
System.out.println("------------------------------");
Flux<Airport> airports = reactiveAirportRepository.findPolicySnapshotAll();
System.out.println(airports.collectList().block());
System.out.println("------------------------------");
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
System.out.println(ap.block());
} finally {
reactiveAirportRepository.delete(saved1).block();
reactiveAirportRepository.delete(saved2).block();
}
}
public Mono<Airport> getPolicyByIdAndEffectiveDateTime(String policyId, Instant effectiveDateTime) {
return reactiveAirportRepository
.findPolicySnapshotByPolicyIdAndEffectiveDateTime(policyId, effectiveDateTime.toEpochMilli())
// .map(Airport::getEntity)
.doOnError(
error -> System.out.println("MSG='Exception happened while retrieving Policy by Id and effectiveDateTime', "
+ "policyId={}, effectiveDateTime={}"));
}
@Test
void count() {
Set<String> iatas = new HashSet();
iatas.add("JFK");
iatas.add("IAD");
iatas.add("SFO");
iatas.add("SJC");
iatas.add("SEA");
iatas.add("LAX");
iatas.add("PHX");
Future[] future = new Future[iatas.size()];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.size());
try {
Callable<Boolean>[] suppliers = new Callable[iatas.size()];
for (String iata : iatas) {
Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */);
reactiveAirportRepository.save(airport).block();
}
int page = 0;
reactiveAirportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) //
.expectNextMatches(a -> {
return iatas.contains(a.getIata());
}).expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete();
reactiveAirportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) //
.expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete();
Long airportCount = reactiveAirportRepository.count().block();
assertEquals(iatas.size(), airportCount);
airportCount = reactiveAirportRepository.countByIataIn("JFK", "IAD", "SFO").block();
assertEquals(3, airportCount);
airportCount = reactiveAirportRepository.countByIcaoAndIataIn("jfk", "JFK", "IAD", "SFO", "XXX").block();
assertEquals(1, airportCount);
airportCount = reactiveAirportRepository.countByIataIn("XXX").block();
assertEquals(0, airportCount);
} finally {
for (String iata : iatas) {
Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */);
try {
reactiveAirportRepository.delete(airport).block();
} catch (DataRetrievalFailureException drfe) {
System.out.println("Failed to delete: " + airport);
}
}
}
}
@Test
// DATACOUCH-650
void deleteAllById() {
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
Airport frankfurt = new Airport("airports::fra", "fra", "EDDX");
Airport losAngeles = new Airport("airports::lax", "lax", "KLAX");
try {
// This failed once against Capella - not sure why.
reactiveAirportRepository.saveAll(asList(vienna, frankfurt, losAngeles)).blockLast();
reactiveAirportRepository.deleteAllById(asList(vienna.getId(), losAngeles.getId())).as(StepVerifier::create)
.verifyComplete();
reactiveAirportRepository.findAll().as(StepVerifier::create).expectNext(frankfurt).verifyComplete();
} finally {
List<Airport> airports = reactiveAirportRepository.findAll().collectList().block(); // .as(StepVerifier::create).expectNext(frankfurt).verifyComplete();
System.out.println(airports);
reactiveAirportRepository.deleteAll().block();
}
}
@Test
void deleteAll() {
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
Airport frankfurt = new Airport("airports::fra", "fra", "EDDY");
Airport losAngeles = new Airport("airports::lax", "lax", "KLAX");
try {
reactiveAirportRepository.saveAll(asList(vienna, frankfurt, losAngeles)).blockLast();
reactiveAirportRepository.deleteAll().as(StepVerifier::create).verifyComplete();
reactiveAirportRepository.findAll().as(StepVerifier::create).verifyComplete();
} finally {
reactiveAirportRepository.deleteAll().block();
}
}
@Test
void deleteOne() {
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
try {
Airport ap = reactiveAirportRepository.save(vienna).block();
assertEquals(vienna.getId(), ap.getId(), "should have saved what was provided");
reactiveAirportRepository.delete(vienna).as(StepVerifier::create).verifyComplete();
reactiveAirportRepository.findAll().as(StepVerifier::create).verifyComplete();
} finally {
reactiveAirportRepository.deleteAll().block();
}
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
}
}

View File

@@ -0,0 +1,425 @@
/*
* Copyright 2017-2022 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.query;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.AddressAnnotated;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.AirportRepositoryAnnotated;
import org.springframework.data.couchbase.domain.CollectionsConfig;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.domain.UserColRepository;
import org.springframework.data.couchbase.domain.UserSubmissionAnnotated;
import org.springframework.data.couchbase.domain.UserSubmissionAnnotatedRepository;
import org.springframework.data.couchbase.domain.UserSubmissionUnannotated;
import org.springframework.data.couchbase.domain.UserSubmissionUnannotatedRepository;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryOptions;
/**
* Repository Query Tests with Collections
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(CollectionsConfig.class)
public class CouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired AirportRepositoryAnnotated airportRepositoryAnnotated;
@Autowired AirportRepository airportRepository;
@Autowired UserColRepository userColRepository;
@Autowired UserSubmissionAnnotatedRepository userSubmissionAnnotatedRepository;
@Autowired UserSubmissionUnannotatedRepository userSubmissionUnannotatedRepository;
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
couchbaseTemplate.removeByQuery(Airport.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inCollection(collectionName2).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).inCollection(collectionName).all();
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
// no-op
// then call the super method
super.afterEach();
}
@Test
void findByKey() {
UserCol userCol = new UserCol("101", "userColFirst", "userColLast");
userColRepository.save(userCol);
UserCol found = userColRepository.getById(userCol.getId());
System.err.println("found: " + found);
assertEquals(userCol, found);
userColRepository.delete(found);
}
@Test
public void myTest() {
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
try {
Airport saved = ar.save(vie);
Airport airport2 = ar.save(saved);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie);
}
}
/**
* can test against _default._default without setting up additional scope/collection and also test for collections and
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
* supports collections
*/
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollection() {
Airport vie = new Airport("airports::vie", "vie", "loww");
// create proxy with scope, collection
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
try {
Airport saved = ar.save(vie);
// valid scope, collection in options
Airport airport2 = ar.withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).iata(vie.getIata());
assertEquals(saved, airport2);
// given bad collectionName in fluent
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()));
// given bad scopeName in fluent
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()));
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).iata(vie.getIata());
assertEquals(saved, airport6);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.deleteAll();
}
}
@Test
void findBySimplePropertyWithOptions() {
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
try {
Airport saved = ar.save(vie);
Airport airport3 = ar
.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata());
assertEquals(saved, airport3);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie);
}
}
@Test
public void testScopeCollectionAnnotation() {
// template default scope is my_scope
// UserCol annotation scope is other_scope
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withCollection(otherCollection).save(user); // should use UserCol annotation
// scope
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname());
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
} finally {
try {
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
public void testScopeCollectionAnnotationSwap() {
// UserCol annotation scope is other_scope, collection is other_collection
// airportRepository relies on Config.setScopeName(scopeName) ("my_scope") from CollectionAwareIntegrationTests.
// using airportRepository without specified a collection should fail.
// This test ensures that airportRepository.save(airport) doesn't get the
// collection from CrudMethodMetadata of UserCol.save()
UserCol userCol = new UserCol("1", "Dave", "Wilson");
Airport airport = new Airport("3", "myIata", "myIcao");
try {
UserCol savedCol = userColRepository.save(userCol); // uses UserCol annotation scope, populates CrudMethodMetadata
userColRepository.delete(userCol); // uses UserCol annotation scope, populates CrudMethodMetadata
assertThrows(IllegalStateException.class, () -> airportRepository.save(airport));
} finally {
List<RemoveResult> removed = couchbaseTemplate.removeByQuery(Airport.class).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).all();
}
}
// template default scope is my_scope
// UserCol annotation scope is other_scope
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user);
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
.findByFirstname(user.getFirstname());
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
} finally {
try {
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
void findPlusN1qlJoinBothAnnotated() throws Exception {
// UserSubmissionAnnotated has scope=my_scope, collection=my_collection
UserSubmissionAnnotated user = new UserSubmissionAnnotated();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user = userSubmissionAnnotatedRepository.save(user);
// AddressesAnnotated has scope=dummy_scope, collection=my_collection2
// scope must be explicitly set on template insertById, findByQuery and removeById
// For userSubmissionAnnotatedRepository.findByUsername(), scope will be taken from UserSubmissionAnnotated
AddressAnnotated address1 = new AddressAnnotated();
address1.setId(UUID.randomUUID().toString());
address1.setStreet("3250 Olcott Street");
address1.setParentId(user.getId());
AddressAnnotated address2 = new AddressAnnotated();
address2.setId(UUID.randomUUID().toString());
address2.setStreet("148 Castro Street");
address2.setParentId(user.getId());
AddressAnnotated address3 = new AddressAnnotated();
address3.setId(UUID.randomUUID().toString());
address3.setStreet("123 Sesame Street");
address3.setParentId(UUID.randomUUID().toString()); // does not belong to user
try {
address1 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address1);
address2 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address2);
address3 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address3);
couchbaseTemplate.findByQuery(AddressAnnotated.class).withConsistency(REQUEST_PLUS).inScope(scopeName).all();
// scope for AddressesAnnotated in N1qlJoin comes from userSubmissionAnnotatedRepository.
List<UserSubmissionAnnotated> users = userSubmissionAnnotatedRepository.findByUsername(user.getUsername());
assertEquals(2, users.get(0).getOtherAddresses().size());
for (Address a : users.get(0).getOtherAddresses()) {
if (!(a.getStreet().equals(address1.getStreet()) || a.getStreet().equals(address2.getStreet()))) {
throw new Exception("street does not match : " + a);
}
}
UserSubmissionAnnotated foundUser = userSubmissionAnnotatedRepository.findById(user.getId()).get();
assertEquals(2, foundUser.getOtherAddresses().size());
for (Address a : foundUser.getOtherAddresses()) {
if (!(a.getStreet().equals(address1.getStreet()) || a.getStreet().equals(address2.getStreet()))) {
throw new Exception("street does not match : " + a);
}
}
} finally {
couchbaseTemplate.removeById(AddressAnnotated.class).inScope(scopeName)
.all(Arrays.asList(address1.getId(), address2.getId(), address3.getId()));
couchbaseTemplate.removeById(UserSubmissionAnnotated.class).one(user.getId());
}
}
@Test
void findPlusN1qlJoinUnannotated() throws Exception {
// UserSubmissionAnnotated has scope=my_scope, collection=my_collection
UserSubmissionUnannotated user = new UserSubmissionUnannotated();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");
user = userSubmissionUnannotatedRepository.save(user);
// AddressesAnnotated has scope=dummy_scope, collection=my_collection2
// scope must be explicitly set on template insertById, findByQuery and removeById
// For userSubmissionAnnotatedRepository.findByUsername(), scope will be taken from UserSubmissionAnnotated
AddressAnnotated address1 = new AddressAnnotated();
address1.setId(UUID.randomUUID().toString());
address1.setStreet("3250 Olcott Street");
address1.setParentId(user.getId());
AddressAnnotated address2 = new AddressAnnotated();
address2.setId(UUID.randomUUID().toString());
address2.setStreet("148 Castro Street");
address2.setParentId(user.getId());
AddressAnnotated address3 = new AddressAnnotated();
address3.setId(UUID.randomUUID().toString());
address3.setStreet("123 Sesame Street");
address3.setParentId(UUID.randomUUID().toString()); // does not belong to user
try {
address1 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address1);
address2 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address2);
address3 = couchbaseTemplate.insertById(AddressAnnotated.class).inScope(scopeName).one(address3);
couchbaseTemplate.findByQuery(AddressAnnotated.class).withConsistency(REQUEST_PLUS).inScope(scopeName).all();
// scope for AddressesAnnotated in N1qlJoin comes from userSubmissionAnnotatedRepository.
List<UserSubmissionUnannotated> users = userSubmissionUnannotatedRepository.findByUsername(user.getUsername());
assertEquals(2, users.get(0).getOtherAddresses().size());
for (Address a : users.get(0).getOtherAddresses()) {
if (!(a.getStreet().equals(address1.getStreet()) || a.getStreet().equals(address2.getStreet()))) {
throw new Exception("street does not match : " + a);
}
}
UserSubmissionUnannotated foundUser = userSubmissionUnannotatedRepository.findById(user.getId()).get();
assertEquals(2, foundUser.getOtherAddresses().size());
for (Address a : foundUser.getOtherAddresses()) {
if (!(a.getStreet().equals(address1.getStreet()) || a.getStreet().equals(address2.getStreet()))) {
throw new Exception("street does not match : " + a);
}
}
} finally {
couchbaseTemplate.removeById(AddressAnnotated.class).inScope(scopeName)
.all(Arrays.asList(address1.getId(), address2.getId(), address3.getId()));
couchbaseTemplate.removeById(UserSubmissionUnannotated.class).one(user.getId());
}
}
@Test
void stringDeleteCollectionTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
airport = airportRepository.withScope(scopeName).withCollection(collectionName).save(airport);
otherAirport = airportRepository.withScope(scopeName).withCollection(collectionName).save(otherAirport);
assertEquals(1,
airportRepository.withScope(scopeName).withCollection(collectionName).deleteByIata(airport.getIata()).size());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
airportRepository.withScope(scopeName).withCollection(collectionName).deleteById(otherAirport.getId());
}
}
@Test
void stringDeleteWithRepositoryAnnotationTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
airport = airportRepositoryAnnotated.withScope(scopeName).save(airport);
otherAirport = airportRepositoryAnnotated.withScope(scopeName).save(otherAirport);
// don't specify a collection - should get collection from AirportRepositoryAnnotated
assertEquals(1, airportRepositoryAnnotated.withScope(scopeName).deleteByIata(airport.getIata()).size());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// this will fail if the above didn't use collectionName2
airportRepository.withScope(scopeName).withCollection(collectionName2).deleteById(otherAirport.getId());
}
}
@Test
void stringDeleteWithMethodAnnotationTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
Airport airportSaved = airportRepositoryAnnotated.withScope(scopeName).save(airport);
Airport otherAirportSaved = airportRepositoryAnnotated.withScope(scopeName).save(otherAirport);
// don't specify a collection - should get collection from deleteByIataAnnotated method
assertThrows(IndexFailureException.class, () -> assertEquals(1,
airportRepositoryAnnotated.withScope(scopeName).deleteByIataAnnotated(airport.getIata()).size()));
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// this will fail if the above didn't use collectionName2
airportRepository.withScope(scopeName).withCollection(collectionName2).deleteById(otherAirport.getId());
}
}
}

View File

@@ -0,0 +1,658 @@
/*
* Copyright 2017-2022 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.query;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.springframework.data.couchbase.util.Util.comprises;
import static org.springframework.data.couchbase.util.Util.exactly;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
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.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.QAirline;
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.support.BasicQuery;
import org.springframework.data.couchbase.repository.support.SpringDataCouchbaseSerializer;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
/**
* Repository tests
*
* @author Michael Reiche
*/
@SpringJUnitConfig(CouchbaseRepositoryQuerydslIntegrationTests.Config.class)
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryQuerydslIntegrationTests extends JavaIntegrationTests {
@Autowired AirlineRepository airlineRepository;
static QAirline airline = QAirline.airline;
// saved
static Airline united = new Airline("1", "United Airlines", "US");
static Airline lufthansa = new Airline("2", "Lufthansa", "DE");
static Airline emptyStringAirline = new Airline("3", "Empty String", "");
static Airline nullStringAirline = new Airline("4", "Null String", null);
static Airline unitedLowercase = new Airline("5", "united airlines", "US");
static Airline[] saved = new Airline[] { united, lufthansa, emptyStringAirline, nullStringAirline, unitedLowercase };
// not saved
static Airline flyByNight = new Airline("1001", "Fly By Night", "UK");
static Airline sleepByDay = new Airline("1002", "Sleep By Day", "CA");
static Airline[] notSaved = new Airline[] { flyByNight, sleepByDay };
SpringDataCouchbaseSerializer serializer = new SpringDataCouchbaseSerializer(couchbaseTemplate.getConverter());
@BeforeAll
static public void beforeAll() {
callSuperBeforeAll(new Object() {});
ApplicationContext ac = new AnnotationConfigApplicationContext(
CouchbaseRepositoryQuerydslIntegrationTests.Config.class);
CouchbaseTemplate template = (CouchbaseTemplate) ac.getBean("couchbaseTemplate");
for (Airline airline : saved) {
template.insertById(Airline.class).one(airline);
}
template.findByQuery(Airline.class).withConsistency(REQUEST_PLUS).all();
}
@AfterAll
static public void afterAll() {
ApplicationContext ac = new AnnotationConfigApplicationContext(
CouchbaseRepositoryQuerydslIntegrationTests.Config.class);
CouchbaseTemplate template = (CouchbaseTemplate) ac.getBean("couchbaseTemplate");
for (Airline airline : saved) {
template.removeById(Airline.class).one(airline.getId());
}
template.findByQuery(Airline.class).withConsistency(REQUEST_PLUS).all();
callSuperAfterAll(new Object() {});
}
@Test
void testEq() {
{
BooleanExpression predicate = airline.name.eq(flyByNight.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().equals(flyByNight.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name = $1", bq(predicate));
}
{
BooleanExpression predicate = airline.name.eq(united.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().equals(united.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name = $1", bq(predicate));
}
}
// this gives hqCountry == "" and hqCountry is missing
// @Test
void testStringIsEmpty() {
{
BooleanExpression predicate = airline.hqCountry.isEmpty();
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result, emptyStringAirline, nullStringAirline), "[unexpected] -> [missing]");
assertEquals(" WHERE UPPER(name) like $1", bq(predicate));
}
}
@Test
void testNot() {
{
BooleanExpression predicate = airline.name.eq(united.getName()).and(airline.hqCountry.eq(united.getHqCountry()))
.not();
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> !(a.getName().equals(united.getName()) && a.getHqCountry().equals(united.getHqCountry())))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE not( ( (hqCountry = $1) and (name = $2)) )", bq(predicate));
}
{
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName())).not();
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> !(a.getName().equals(united.getName()))).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE not( (name = $1) )", bq(predicate));
}
{
BooleanExpression predicate = airline.name.eq(united.getName()).not();
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> !(a.getName().equals(united.getName()))).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE not( (name = $1) )", bq(predicate));
}
}
@Test
void testAnd() {
{
BooleanExpression predicate = airline.name.eq(united.getName()).and(airline.hqCountry.eq(united.getHqCountry()));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().equals(united.getName()) && a.getHqCountry().equals(united.getHqCountry()))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE (name = $1) and (hqCountry = $2)", bq(predicate));
}
{
BooleanExpression predicate = airline.name.eq(united.getName())
.and(airline.hqCountry.eq(lufthansa.getHqCountry()));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().equals(united.getName()) && a.getHqCountry().equals(lufthansa.getHqCountry()))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE (name = $1) and (hqCountry = $2)", bq(predicate));
}
}
@Test
void testOr() {
{
BooleanExpression predicate = airline.name.eq(united.getName())
.or(airline.hqCountry.eq(lufthansa.getHqCountry()));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().equals(united.getName()) || a.getName().equals(lufthansa.getName()))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE (name = $1) or (hqCountry = $2)", bq(predicate));
}
}
@Test
void testNe() {
{
BooleanExpression predicate = airline.name.ne(united.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> !a.getName().equals(united.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name != $1", bq(predicate));
}
}
@Test
void testStartsWith() {
{
BooleanExpression predicate = airline.name.startsWith(united.getName().substring(0, 5));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result, Arrays.stream(saved)
.filter(a -> a.getName().startsWith(united.getName().substring(0, 5))).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name like ($1||\"%\")", bq(predicate));
}
}
@Test
void testStartsWithIgnoreCase() {
{
BooleanExpression predicate = airline.name.startsWithIgnoreCase(united.getName().substring(0, 5));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().toUpperCase().startsWith(united.getName().toUpperCase().substring(0, 5)))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE lower(name) like ($1||\"%\")", bq(predicate));
}
}
@Test
void testEndsWith() {
{
BooleanExpression predicate = airline.name.endsWith(united.getName().substring(1));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result, Arrays.stream(saved).filter(a -> a.getName().endsWith(united.getName().substring(1)))
.toArray(Airline[]::new)), "[unexpected] -> [missing]");
assertEquals(" WHERE name like (\"%\"||$1)", bq(predicate));
}
}
@Test
void testEndsWithIgnoreCase() {
{
BooleanExpression predicate = airline.name.endsWithIgnoreCase(united.getName().substring(1));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().toUpperCase().endsWith(united.getName().toUpperCase().substring(1)))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE lower(name) like (\"%\"||$1)", bq(predicate));
}
}
@Test
void testEqIgnoreCase() {
{
BooleanExpression predicate = airline.name.equalsIgnoreCase(flyByNight.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved).filter(a -> a.getName().equalsIgnoreCase(flyByNight.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE lower(name) = $1", bq(predicate));
}
{
BooleanExpression predicate = airline.name.equalsIgnoreCase(united.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().equalsIgnoreCase(united.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE lower(name) = $1", bq(predicate));
}
}
@Test
void testContains() {
{
BooleanExpression predicate = airline.name.contains("United");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result, Arrays.stream(saved).filter(a -> a.getName().contains("United")).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE contains(name, $1)", bq(predicate));
}
}
@Test
void testContainsIgnoreCase() {
{
BooleanExpression predicate = airline.name.containsIgnoreCase("united");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().toUpperCase(Locale.ROOT).contains("united".toUpperCase(Locale.ROOT)))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE contains(lower(name), $1)", bq(predicate));
}
}
@Test
void testLike() {
{
BooleanExpression predicate = airline.name.like("%nited%");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result, Arrays.stream(saved).filter(a -> a.getName().contains("nited")).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name like $1", bq(predicate));
}
}
@Test
void testLikeIgnoreCase() {
{
BooleanExpression predicate = airline.name.likeIgnoreCase("%Airlines");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().toUpperCase(Locale.ROOT).endsWith("Airlines".toUpperCase(Locale.ROOT)))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE lower(name) like $1", bq(predicate));
}
}
// This is 'between' is inclusive
@Test
void testBetween() {
{
BooleanExpression predicate = airline.name.between(flyByNight.getName(), united.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(
a -> a.getName().compareTo(flyByNight.getName()) >= 0 && a.getName().compareTo(united.getName()) <= 0)
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name between $1 and $2", bq(predicate));
}
}
@Test
void testIn() {
{
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName()));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().equals(united.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name = $1", bq(predicate));
}
{
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName(), lufthansa.getName()));
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().equals(united.getName()) || a.getName().equals(lufthansa.getName()))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name in $1", bq(predicate));
}
{
BooleanExpression predicate = airline.name.in("Fly By Night", "Sleep By Day");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> a.getName().equals(flyByNight.getName()) || a.getName().equals(sleepByDay.getName()))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name in $1", bq(predicate));
}
}
@Test
void testNotIn() {
{
BooleanExpression predicate = airline.name.notIn("Fly By Night", "Sleep By Day");
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved)
.filter(a -> !(a.getName().equals(flyByNight.getName()) || a.getName().equals(sleepByDay.getName())))
.toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE not( (name in $1) )", bq(predicate));
}
{
BooleanExpression predicate = airline.name.notIn(united.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> !a.getName().equals(united.getName())).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name != $1", bq(predicate));
}
}
@Test
@Disabled
void testColIsEmpty() {}
@Test
void testLt() {
{
BooleanExpression predicate = airline.name.lt(lufthansa.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) < 0).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name < $1", bq(predicate));
}
}
@Test
void testGt() {
{
BooleanExpression predicate = airline.name.gt(lufthansa.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(
comprises(result,
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) > 0).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name > $1", bq(predicate));
}
}
@Test
void testLoe() {
{
BooleanExpression predicate = airline.name.loe(lufthansa.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) <= 0).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name <= $1", bq(predicate));
}
}
@Test
void testGoe() {
{
BooleanExpression predicate = airline.name.goe(lufthansa.getName());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) >= 0).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE name >= $1", bq(predicate));
}
}
// when hqCountry == null, no value is stored therefore isNull is false. Only hqCountry:null gives isNull
// and we don't have that. Conversely, only hqCountry has a value (which is not 'null') gives isNotNull
// so isNull and isNotNull are *not* compliments
@Test
@Disabled
void testIsNull() {
{
BooleanExpression predicate = airline.hqCountry.isNull();
Optional<Airline> result = airlineRepository.findOne(predicate);
assertNull(exactly(result, nullStringAirline), "[unexpected] -> [missing]");
assertEquals(" WHERE name = $1", bq(predicate));
}
}
// when hqCountry == null, no value is stored therefore isNull is false. Only hqCountry:null gives isNull
// and we don't have that. Conversely, only hqCountry has a value (which is not 'null') gives isNotNull
// so isNull and isNotNull are *not* compliments
@Test
void testIsNotNull() {
{
BooleanExpression predicate = airline.hqCountry.isNotNull();
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result, Arrays.stream(saved).filter(a -> a.getHqCountry() != null).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE hqCountry is not null", bq(predicate));
}
}
@Test
@Disabled
void testContainsKey() {}
@Test
void testStringLength() {
{
BooleanExpression predicate = airline.name.length().eq(united.getName().length());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result,
Arrays.stream(saved).filter(a -> a.getName().length() == united.getName().length()).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE LENGTH(name) = $1", bq(predicate));
}
{
BooleanExpression predicate = airline.name.length().eq(flyByNight.getName().length());
Iterable<Airline> result = airlineRepository.findAll(predicate);
assertNull(comprises(result, Arrays.stream(saved)
.filter(a -> a.getName().length() == flyByNight.getName().length()).toArray(Airline[]::new)),
"[unexpected] -> [missing]");
assertEquals(" WHERE LENGTH(name) = $1", bq(predicate));
}
}
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order
} catch (InterruptedException ie) {
;
}
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
@EnableCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Bean(name = "auditorAwareRef")
public NaiveAuditorAware testAuditorAware() {
return new NaiveAuditorAware();
}
@Override
public void configureEnvironment(final ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
@Bean
public ValidatingCouchbaseEventListener validationEventListener() {
return new ValidatingCouchbaseEventListener(validator());
}
}
String bq(Predicate predicate) {
BasicQuery basicQuery = new BasicQuery((QueryCriteriaDefinition) serializer.handle(predicate), null);
return basicQuery.export(new int[1]);
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
static class ConfigRequestPlus extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Bean(name = "auditorAwareRef")
public NaiveAuditorAware testAuditorAware() {
return new NaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
@Override
public QueryScanConsistency getDefaultConsistency() {
return REQUEST_PLUS;
}
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2017-2022 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.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
import static org.springframework.data.couchbase.core.query.QueryCriteria.where;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Query;
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.mapping.context.MappingContext;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.PartTree;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
/**
* @author Michael Nitschinger
* @author Michael Reiche
* @author Mauro Monti
*/
class N1qlQueryCreatorTests {
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
CouchbaseConverter converter;
String bucketName;
@BeforeEach
public void beforeEach() {
context = new CouchbaseMappingContext();
converter = new MappingCouchbaseConverter(context);
bucketName = "sample-bucket";
}
@Test
void createsQueryCorrectly() throws Exception {
String input = "findByFirstname";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where(i("firstname")).is("Oliver").export());
}
@Test
void createsQueryCorrectlyIgnoreCase() throws Exception {
String input = "findByFirstnameIgnoreCase";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
assertEquals(query.export(),
" WHERE " + where("lower(" + i("firstname") + ")").is("Oliver".toLowerCase(Locale.ROOT)).export());
}
@Test
void createsQueryFieldAnnotationCorrectly() throws Exception {
String input = "findByMiddlename";
PartTree tree = new PartTree(input, Person.class);
Method method = PersonRepository.class.getMethod(input, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where(i("nickname")).is("Oliver").export());
}
@Test
void queryParametersArray() throws Exception {
String input = "findByFirstnameIn";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String[].class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
assertEquals(" WHERE `firstname` in $1", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}
@Test
void queryParametersJsonArray() throws Exception {
String input = "findByFirstnameIn";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, JsonArray.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
JsonArray jsonArray = JsonArray.create();
jsonArray.add("Oliver");
jsonArray.add("Charles");
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
assertEquals(" WHERE `firstname` in $1", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}
@Test
void queryParametersList() throws Exception {
String input = "findByFirstnameIn";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String[].class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
List<String> list = new LinkedList<>();
list.add("Oliver");
list.add("Charles");
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), new Object[] { list }),
queryMethod, converter, bucketName);
Query query = creator.createQuery();
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
assertEquals(" WHERE `firstname` in $1", query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}
@Test
void createsAndQueryCorrectly() throws Exception {
String input = "findByFirstnameAndLastname";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"),
queryMethod, converter, bucketName);
Query query = creator.createQuery();
assertEquals(" WHERE " + where(i("firstname")).is("John").and(i("lastname")).is("Doe").export(), query.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1072
void createsQueryFindByIdIsNotNullAndFirstname() throws Exception {
String input = "findByIdIsNotNullAndFirstnameEquals";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), queryMethod,
converter, bucketName);
Query query = creator.createQuery();
assertEquals(" WHERE " + where(x("META().`id`")).isNotNull().and(i("firstname")).is("Oliver").export(),
query.export());
}
@Test // https://github.com/spring-projects/spring-data-couchbase/issues/1072
void createsQueryFindByVersionEqualsAndAndFirstname() throws Exception {
String input = "findByVersionEqualsAndFirstnameEquals";
PartTree tree = new PartTree(input, User.class);
Method method = UserRepository.class.getMethod(input, Long.class, String.class);
QueryMethod queryMethod = new QueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory());
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
getAccessor(getParameters(method), 1611287177404088320L, "Oliver"), queryMethod, converter, bucketName);
Query query = creator.createQuery();
assertEquals(
" WHERE " + where(x("META().`cas`")).is(1611287177404088320L).and(i("firstname")).is("Oliver").export(),
query.export());
}
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
return new ParametersParameterAccessor(params, values);
}
private Parameters<?, ?> getParameters(Method method) {
return new DefaultParameters(method);
}
}

View File

@@ -0,0 +1,278 @@
/*
* Copyright 2017-2022 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.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.CollectionsConfig;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
import org.springframework.data.couchbase.domain.ReactiveAirportRepositoryAnnotated;
import org.springframework.data.couchbase.domain.ReactiveUserColRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Reactive Repository Query Tests with Collections
*
* @author Michael Reiche
*/
@SpringJUnitConfig(CollectionsConfig.class)
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired ReactiveAirportRepository reactiveAirportRepository;
@Autowired ReactiveAirportRepositoryAnnotated reactiveAirportRepositoryAnnotated;
@Autowired ReactiveUserColRepository userColRepository;
@Autowired public CouchbaseTemplate couchbaseTemplate;
@Autowired public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
// no-op
// then call the super method
super.afterEach();
}
@Test
public void myTest() {
ReactiveAirportRepository ar = reactiveAirportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
try {
Airport saved = ar.save(vie).block();
Airport airport2 = ar.save(saved).block();
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie).block();
}
}
/**
* can test against _default._default without setting up additional scope/collection and also test for collections and
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
* supports collections
*/
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollection() {
Airport vie = new Airport("airports::vie", "vie", "loww");
// create proxy with scope, collection
ReactiveAirportRepository ar = reactiveAirportRepository.withScope(scopeName).withCollection(collectionName);
try {
Airport saved = ar.save(vie).block();
// valid scope, collection in options
Airport airport2 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata()).block();
assertEquals(saved, airport2);
// given bad collectionName in fluent
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()).block());
// given bad scopeName in fluent
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()).block());
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata()).block();
assertEquals(saved, airport6);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.deleteAll().block();
}
}
@Test
void findBySimplePropertyWithOptions() {
Airport vie = new Airport("airports::vie", "vie", "loww");
ReactiveAirportRepository ar = reactiveAirportRepository.withScope(scopeName).withCollection(collectionName);
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
try {
Airport saved = ar.save(vie).block();
Airport airport3 = ar.withOptions(
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata()).block();
assertEquals(saved, airport3);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie).block();
}
}
@Test
public void testScopeCollectionAnnotation() {
// template default scope is my_scope
// UserCol annotation scope is other_scope
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withCollection(otherCollection).save(user).block(); // should use UserCol
// annotation
// scope
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname())
.collectList().block();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
.block();
assertEquals(0, notfound.size(), "should not have found what was saved");
} finally {
try {
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
// template default scope is my_scope
// UserCol annotation scope is other_scope
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user).block();
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
.findByFirstname(user.getFirstname()).collectList().block();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
.block();
assertEquals(0, notfound.size(), "should not have found what was saved");
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
} finally {
try {
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
void stringDeleteCollectionTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
airport = reactiveAirportRepository.withScope(scopeName).withCollection(collectionName).save(airport).block();
otherAirport = reactiveAirportRepository.withScope(scopeName).withCollection(collectionName).save(otherAirport)
.block();
assertEquals(1, reactiveAirportRepository.withScope(scopeName).withCollection(collectionName)
.deleteByIata(airport.getIata()).collectList().block().size());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
reactiveAirportRepository.withScope(scopeName).withCollection(collectionName).deleteById(otherAirport.getId());
}
}
@Test
void stringDeleteWithRepositoryAnnotationTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
airport = reactiveAirportRepositoryAnnotated.withScope(scopeName).save(airport).block();
otherAirport = reactiveAirportRepositoryAnnotated.withScope(scopeName).save(otherAirport).block();
// don't specify a collection - should get collection from AirportRepositoryAnnotated
assertEquals(1, reactiveAirportRepositoryAnnotated.withScope(scopeName).deleteByIata(airport.getIata())
.collectList().block().size());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// this will fail if the above didn't use collectionName2
reactiveAirportRepository.withScope(scopeName).withCollection(collectionName2).deleteById(otherAirport.getId());
}
}
@Test
void stringDeleteWithMethodAnnotationTest() {
Airport airport = new Airport(loc(), "vie", "abc");
Airport otherAirport = new Airport(loc(), "xxx", "xyz");
try {
Airport airportSaved = reactiveAirportRepositoryAnnotated.withScope(scopeName).save(airport).block();
Airport otherAirportSaved = reactiveAirportRepositoryAnnotated.withScope(scopeName).save(otherAirport).block();
// don't specify a collection - should get collection from deleteByIataAnnotated method
assertThrows(IndexFailureException.class, () -> assertEquals(1, reactiveAirportRepositoryAnnotated
.withScope(scopeName).deleteByIataAnnotated(airport.getIata()).collectList().block().size()));
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
// this will fail if the above didn't use collectionName2
reactiveAirportRepository.withScope(scopeName).withCollection(collectionName2).deleteById(otherAirport.getId());
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2017-2022 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.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
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.mapping.context.MappingContext;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* @author Michael Nitschinger
* @author Michael Reiche
*/
@SpringJUnitConfig(StringN1qlQueryCreatorIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
class StringN1qlQueryCreatorIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
@Autowired CouchbaseConverter converter;
@Autowired CouchbaseTemplate couchbaseTemplate;
static NamedQueries namedQueries = new PropertiesBasedNamedQueries(new Properties());
@BeforeEach
public void beforeEach() {}
@Test
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
void findUsingStringNq1l() throws Exception {
Airline airline = new Airline(UUID.randomUUID().toString(), "Continental", "USA");
try {
Airline modified = couchbaseTemplate.upsertById(Airline.class).one(airline);
String input = "getByName";
Method method = AirlineRepository.class.getMethod(input, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(AirlineRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
Query query = creator.createQuery();
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate.findByQuery(Airline.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query);
Optional<Airline> al = q.one();
assertEquals(airline.toString(), al.get().toString());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
couchbaseTemplate.removeById().one(airline.getId());
}
}
@Test
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
void findUsingStringNq1l_3x_projection_id_cas() throws Exception {
Airline airline = new Airline(UUID.randomUUID().toString(), "Continental", "USA");
try {
Airline modified = couchbaseTemplate.upsertById(Airline.class).one(airline);
String input = "getByName_3x";
Method method = AirlineRepository.class.getMethod(input, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(AirlineRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
Query query = creator.createQuery();
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate.findByQuery(Airline.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query);
Optional<Airline> al = q.one();
assertEquals(airline.toString(), al.get().toString());
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
couchbaseTemplate.removeById().one(airline.getId());
}
}
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
return new ParametersParameterAccessor(params, values);
}
private Parameters<?, ?> getParameters(Method method) {
return new DefaultParameters(method);
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Override
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
if (config().isUsingCloud()) {
builder.securityConfig(
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
}
}
@Override
protected boolean autoIndexCreation() {
return true;
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2017-2022 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.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.reflect.Method;
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* @author Michael Nitschinger
* @author Michael Reiche
*/
class StringN1qlQueryCreatorTests {
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
CouchbaseConverter converter;
static NamedQueries namedQueries = new PropertiesBasedNamedQueries(new Properties());
@BeforeEach
public void beforeEach() {
context = new CouchbaseMappingContext();
converter = new MappingCouchbaseConverter(context);
}
@Test
void wrongNumberArgs() throws Exception {
String input = "getByFirstnameOrLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
} catch (IllegalArgumentException e) {
return;
}
fail("should have failed with IllegalArgumentException: Invalid number of parameters given!");
}
@Test
void doesNotHaveAnnotation() throws Exception {
String input = "findByFirstname";
Method method = UserRepository.class.getMethod(input, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT,
namedQueries);
} catch (IllegalArgumentException e) {
return;
}
fail("should have failed with IllegalArgumentException: query has no inline Query or named Query not found");
}
@Test
void createsQueryCorrectly() throws Exception {
String input = "getByFirstnameAndLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
"SELECT `_class`, META(`" + bucketName()
+ "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`"
+ bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName()
+ "` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2",
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null));
}
@Test
void createsQueryCorrectly2() throws Exception {
String input = "getByFirstnameOrLastname";
Method method = UserRepository.class.getMethod(input, String.class, String.class);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
queryMethod, converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
"SELECT `_class`, META(`" + bucketName()
+ "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`"
+ bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName()
+ "` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)",
query.toN1qlSelectString(converter, bucketName(), null, null, User.class, User.class, false, null, null));
}
@Test
void spelTests() throws Exception {
String input = "spelTests";
Method method = UserRepository.class.getMethod(input);
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method,
new DefaultRepositoryMetadata(UserRepository.class), new SpelAwareProxyProjectionFactory(),
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method)), queryMethod,
converter, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
"SELECT `_class`, META(`myCollection`).`cas` AS __cas, `createdBy`, `createdDate`, "
+ "`lastModifiedBy`, `lastModifiedDate`, META(`myCollection`).`id` AS __id, `firstname`, "
+ "`lastname`, `subtype` FROM `myCollection`|`_class` = \"abstractuser\""
+ "|`myCollection`|`myScope`|`myCollection`",
query.toN1qlSelectString(converter, bucketName(), "myScope", "myCollection", User.class, null, false, null,
null));
}
private String bucketName() {
return "some_bucket";
}
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
return new ParametersParameterAccessor(params, values);
}
private Parameters<?, ?> getParameters(Method method) {
return new DefaultParameters(method);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2022 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.transactions;
import lombok.Data;
import org.springframework.data.domain.Persistable;
/**
* For testing transactions.
*
* @author Michael Reiche
*/
@Data
public class AfterTransactionAssertion<T extends Persistable> {
private final T persistable;
private boolean expectToBePresent;
public void isPresent() {
expectToBePresent = true;
}
public void isNotPresent() {
expectToBePresent = false;
}
public Object getId() {
return persistable.getId();
}
public boolean shouldBePresent() {
return expectToBePresent;
}
}

View File

@@ -0,0 +1,348 @@
/*
* Copyright 2022 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.transactions;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import lombok.Data;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.TransactionalSupport;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.ReactivePersonRepository;
import org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException;
import org.springframework.data.couchbase.transactions.util.TransactionTestUtil;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.reactive.TransactionalOperator;
import com.couchbase.client.core.error.DocumentExistsException;
import com.couchbase.client.java.transactions.TransactionResult;
/**
* Tests for com.couchbase.transactions using
* <li><le>couchbase reactive transaction manager via transactional operator</le> <le>couchbase non-reactive transaction
* manager via @Transactional</le> <le>@Transactional(transactionManager =
* BeanNames.REACTIVE_COUCHBASE_TRANSACTION_MANAGER)</le></li>
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(classes = { TransactionsConfig.class, PersonService.class })
public class CouchbasePersonTransactionIntegrationTests extends JavaIntegrationTests {
// intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated.
@Autowired CouchbaseClientFactory couchbaseClientFactory;
@Autowired PersonRepository repo;
@Autowired ReactivePersonRepository rxRepo;
@Autowired CouchbaseTemplate cbTmpl;
@Autowired ReactiveCouchbaseTemplate rxCBTmpl;
@Autowired PersonService personService;
@Autowired TransactionalOperator transactionalOperator;
String sName = "_default";
String cName = "_default";
Person WalterWhite;
@BeforeAll
public static void beforeAll() {
callSuperBeforeAll(new Object() {});
}
@AfterAll
public static void afterAll() {
callSuperAfterAll(new Object() {});
}
@AfterEach
public void afterEachTest() {
TransactionTestUtil.assertNotInTransaction();
}
@BeforeEach
public void beforeEachTest() {
WalterWhite = new Person("Walter", "White");
TransactionTestUtil.assertNotInTransaction();
List<RemoveResult> rp0 = cbTmpl.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all();
List<RemoveResult> rp1 = cbTmpl.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).inScope(sName)
.inCollection(cName).all();
List<RemoveResult> rp2 = cbTmpl.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all();
List<RemoveResult> rp3 = cbTmpl.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).inScope(sName)
.inCollection(cName).all();
List<Person> p0 = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all();
List<Person> p1 = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).inScope(sName).inCollection(cName)
.all();
List<EventLog> e0 = cbTmpl.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all();
List<EventLog> e1 = cbTmpl.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).inScope(sName)
.inCollection(cName).all();
}
@DisplayName("rollback after exception using transactionalOperator")
@Test
public void shouldRollbackAfterException() {
assertThrowsWithCause(() -> personService.savePersonErrors(WalterWhite),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(0, count, "should have done roll back and left 0 entries");
}
@Test
@DisplayName("rollback after exception using @Transactional")
public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() {
assertThrowsWithCause(() -> personService.declarativeSavePersonErrors(WalterWhite),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(0, count, "should have done roll back and left 0 entries");
}
@Test
@DisplayName("rollback after exception after using @Transactional(reactive)")
public void shouldRollbackAfterExceptionOfTxAnnotatedMethodReactive() {
assertThrowsWithCause(() -> personService.declarativeSavePersonErrorsReactive(WalterWhite).block(),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(0, count, "should have done roll back and left 0 entries");
}
@Test
public void commitShouldPersistTxEntries() {
Person p = personService.savePerson(WalterWhite);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(1, count, "should have saved and found 1");
}
@Test
public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() {
Person p = personService.declarativeSavePerson(WalterWhite);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(1, count, "should have saved and found 1");
}
@Test
/**
* This fails with TransactionOperationFailedException {ec:FAIL_CAS_MISMATCH, retry:true, autoRollback:true}. I don't
* know why it isn't retried. This seems like it is due to the functioning of AbstractPlatformTransactionManager
*/
public void replaceInTxAnnotatedCallback() {
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
Person switchedPerson = new Person(person.getId(), "Dave", "Reynolds");
AtomicInteger tryCount = new AtomicInteger(0);
Person p = personService.declarativeFindReplacePersonCallback(switchedPerson, tryCount);
Person pFound = cbTmpl.findById(Person.class).one(person.id());
assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched");
}
@Test
public void commitShouldPersistTxEntriesOfTxAnnotatedMethodReactive() {
Person p = personService.declarativeSavePersonReactive(WalterWhite).block();
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(1, count, "should have saved and found 1");
}
@Test
public void commitShouldPersistTxEntriesAcrossCollections() {
List<EventLog> persons = personService.saveWithLogs(WalterWhite);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(1, count, "should have saved and found 1");
Long countEvents = cbTmpl.count(new Query(), EventLog.class); //
assertEquals(4, countEvents, "should have saved and found 4");
}
@Test
public void rollbackShouldAbortAcrossCollections() {
assertThrowsWithCause(() -> personService.saveWithErrorLogs(WalterWhite),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
List<Person> persons = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all();
assertEquals(0, persons.size(), "should have done roll back and left 0 entries");
List<EventLog> events = cbTmpl.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all(); //
assertEquals(0, events.size(), "should have done roll back and left 0 entries");
}
@Test
public void countShouldWorkInsideTransaction() {
Long count = personService.countDuringTx(WalterWhite);
assertEquals(1, count, "should have counted 1 during tx");
}
@Test
public void emitMultipleElementsDuringTransaction() {
List<EventLog> docs = personService.saveWithLogs(WalterWhite);
assertEquals(4, docs.size(), "should have found 4 eventlogs");
}
@Test
public void errorAfterTxShouldNotAffectPreviousStep() {
Person p = personService.savePerson(WalterWhite);
assertThrowsOneOf(() -> personService.savePerson(p), TransactionSystemUnambiguousException.class,
DocumentExistsException.class);
Long count = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count();
assertEquals(1, count, "should have saved and found 1");
}
@Test
public void replacePersonCBTransactionsRxTmpl() {
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
Mono<Person> result = rxCBTmpl.findById(Person.class).one(person.id()) //
.flatMap(pp -> rxCBTmpl.replaceById(Person.class).one(pp)).doOnNext(ppp -> TransactionalSupport
.checkForTransactionInThreadLocalStorage().doOnNext(v -> assertTrue(v.isPresent())))
.as(transactionalOperator::transactional);
result.block();
Person pFound = cbTmpl.findById(Person.class).one(person.id());
assertEquals(person, pFound, "should have found expected " + person);
}
@Test
public void insertPersonCBTransactionsRxTmplRollback() {
Mono<Person> result = rxCBTmpl.insertById(Person.class).one(WalterWhite) //
.doOnNext(ppp -> TransactionalSupport.checkForTransactionInThreadLocalStorage()
.doOnNext(v -> assertTrue(v.isPresent())))
.map(p -> throwSimulateFailureException(p)).as(transactionalOperator::transactional); // tx
assertThrowsWithCause(result::block, TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).one(WalterWhite.id());
assertNull(pFound, "insert should have been rolled back");
}
@Test
public void insertTwicePersonCBTransactionsRxTmplRollback() {
Mono<Person> result = rxCBTmpl.insertById(Person.class).one(WalterWhite) //
.flatMap(ppp -> rxCBTmpl.insertById(Person.class).one(ppp)) //
.as(transactionalOperator::transactional);
assertThrowsWithCause(result::block, TransactionSystemUnambiguousException.class, DuplicateKeyException.class);
Person pFound = cbTmpl.findById(Person.class).one(WalterWhite.id());
assertNull(pFound, "insert should have been rolled back");
}
/**
* I think this test might fail sometimes? Does it need retryWhen() ?
*/
@Disabled("todo gp: disabling temporarily as hanging intermittently")
@Test
public void wrapperReplaceWithCasConflictResolvedViaRetry() {
AtomicInteger tryCount = new AtomicInteger();
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
String newName = "Dave";
TransactionResult txResult = couchbaseClientFactory.getCluster().transactions().run(ctx -> {
Person ppp = cbTmpl.findById(Person.class).one(person.id());
ReplaceLoopThread.updateOutOfTransaction(cbTmpl, person, tryCount.incrementAndGet());
Person pppp = cbTmpl.replaceById(Person.class).one(ppp.withFirstName(newName));
});
Person pFound = cbTmpl.findById(Person.class).one(person.id());
assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get());
assertEquals(newName, pFound.getFirstname(), "should have been switched");
}
/**
* This does process retries - by CallbackTransactionManager.execute() -> transactions.run() -> executeTransaction()
* -> retryWhen.
*/
/**
* This fails with TransactionOperationFailedException {ec:FAIL_CAS_MISMATCH, retry:true, autoRollback:true}. I don't
* know why it isn't retried. This seems like it is due to the functioning of AbstractPlatformTransactionManager
*/
@Test
public void replaceWithCasConflictResolvedViaRetryAnnotatedCallback() {
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
Person switchedPerson = new Person(person.getId(), "Dave", "Reynolds");
AtomicInteger tryCount = new AtomicInteger();
Person p = personService.declarativeFindReplacePersonCallback(switchedPerson, tryCount);
Person pFound = cbTmpl.findById(Person.class).one(person.id());
assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched");
assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get());
}
/**
* Reactive @Transactional does not retry write-write conflicts. It throws RetryTransactionException up to the client
* and expects the client to retry.
*/
@Test
public void replaceWithCasConflictResolvedViaRetryAnnotatedReactive() {
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
Person switchedPerson = new Person(person.getId(), "Dave", "Reynolds");
AtomicInteger tryCount = new AtomicInteger();
Person res = personService.declarativeFindReplacePersonReactive(switchedPerson, tryCount).block();
Person pFound = cbTmpl.findById(Person.class).one(person.id());
assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched");
assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get());
}
@Test
public void replaceWithCasConflictResolvedViaRetryAnnotated() {
Person person = cbTmpl.insertById(Person.class).one(WalterWhite);
Person switchedPerson = person.withFirstName("Dave");
AtomicInteger tryCount = new AtomicInteger();
Person p = personService.declarativeFindReplacePerson(switchedPerson, tryCount);
Person pFound = cbTmpl.findById(Person.class).one(person.id());
System.out.println("pFound: " + pFound);
assertEquals(switchedPerson.getFirstname(), pFound.getFirstname(), "should have been switched");
assertTrue(tryCount.get() > 1, "should have been more than one try. tries: " + tryCount.get());
}
@Data
static class EventLog {
public EventLog() {}; // don't remove this
public EventLog(ObjectId oid, String action) {
this.id = oid.toString();
this.action = action;
}
String id;
String action;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("EventLog : {\n");
sb.append(" id : " + getId());
sb.append(", action: " + action);
return sb.toString();
}
}
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright 2022 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.transactions;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import lombok.Data;
import org.springframework.data.couchbase.domain.PersonWithoutVersion;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.ReactivePersonRepository;
import org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException;
import org.springframework.data.couchbase.transactions.util.TransactionTestUtil;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.java.Cluster;
/**
* todo gp: these tests are using the `.as(transactionalOperator::transactional)` method which is for the chopping
* block, so presumably these tests are too todo mr: I'm not sure how as(transactionalOperator::transactional) is
* different than todo mr: transactionOperator.transaction(...)in CouchbaseTransactionalOperatorTemplateIntegrationTests
* ?
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(classes = { TransactionsConfig.class, PersonServiceReactive.class })
public class CouchbasePersonTransactionReactiveIntegrationTests extends JavaIntegrationTests {
// intellij flags "Could not autowire" when config classes are specified with classes={...}. But they are populated.
@Autowired CouchbaseClientFactory couchbaseClientFactory;
@Autowired ReactivePersonRepository rxRepo;
@Autowired PersonRepository repo;
@Autowired CouchbaseTemplate cbTmpl;
@Autowired ReactiveCouchbaseTemplate rxCBTmpl;
@Autowired Cluster myCluster;
@Autowired PersonServiceReactive personService;
@Autowired ReactiveCouchbaseTemplate operations;
// if these are changed from default, then beforeEach needs to clean up separately
String sName = "_default";
String cName = "_default";
Person WalterWhite;
PersonWithoutVersion BobbyBlackWithoutVersion;
@BeforeAll
public static void beforeAll() {
callSuperBeforeAll(new Object() {});
}
@AfterAll
public static void afterAll() {
callSuperAfterAll(new Object() {});
}
@BeforeEach
public void beforeEachTest() {
WalterWhite = new Person("Walter", "White");
BobbyBlackWithoutVersion = new PersonWithoutVersion("Bobby", "Black");
TransactionTestUtil.assertNotInTransaction();
List<RemoveResult> pr = operations.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList()
.block();
List<RemoveResult> er = operations.removeByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList()
.block();
List<Person> p = operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all().collectList().block();
List<EventLog> e = operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).all().collectList().block();
}
@Test
public void shouldRollbackAfterException() {
personService.savePersonErrors(WalterWhite) //
.as(StepVerifier::create) //
.verifyError(TransactionSystemUnambiguousException.class);
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(0L) //
.verifyComplete();
}
@Test
public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() {
assertThrowsWithCause(() -> personService.declarativeSavePersonErrors(WalterWhite).block(),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
}
@Test
public void commitShouldPersistTxEntries() {
personService.savePerson(WalterWhite) //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
}
@Test
public void commitShouldPersistTxEntriesOfTxAnnotatedMethod() {
personService.declarativeSavePerson(WalterWhite).as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
}
@Test
public void commitShouldPersistTxEntriesOfTxAnnotatedMethodNoVersion() {
personService.declarativeSavePersonWithoutVersion(BobbyBlackWithoutVersion).as(StepVerifier::create) //
.expectError(UnsupportedOperationException.class); //
}
@Test
public void commitShouldPersistTxEntriesAcrossCollections() {
personService.saveWithLogs(WalterWhite) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(4L) //
.verifyComplete();
}
@Test
public void rollbackShouldAbortAcrossCollections() {
personService.saveWithErrorLogs(WalterWhite) //
.then() //
.as(StepVerifier::create) //
.verifyError();
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(0L) //
.verifyComplete();
operations.findByQuery(EventLog.class).withConsistency(REQUEST_PLUS).count()//
.as(StepVerifier::create) //
.expectNext(0L) //
.verifyComplete();
}
@Test
public void countShouldWorkInsideTransaction() {
personService.countDuringTx(WalterWhite) //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
}
@Test
public void emitMultipleElementsDuringTransaction() {
personService.saveWithLogs(WalterWhite) //
.as(StepVerifier::create) //
.expectNextCount(4L) //
.verifyComplete();
}
@Test
public void errorAfterTxShouldNotAffectPreviousStep() {
personService.savePerson(WalterWhite) //
.then(Mono.error(new SimulateFailureException())).as(StepVerifier::create) //
.verifyError();
operations.findByQuery(Person.class).withConsistency(REQUEST_PLUS).count() //
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
}
@Data
// @AllArgsConstructor
static class EventLog {
public EventLog() {}
public EventLog(ObjectId oid, String action) {
this.id = oid.toString();
this.action = action;
}
public EventLog(String id, String action) {
this.id = id;
this.action = action;
}
String id;
String action;
@Version Long version;
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2022 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.transactions;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.ReactivePersonRepository;
import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator;
import org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException;
import org.springframework.data.couchbase.transactions.util.TransactionTestUtil;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.reactive.TransactionalOperator;
/**
* Tests for CouchbaseTransactionalOperator.
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(TransactionsConfig.class)
public class CouchbaseReactiveTransactionNativeIntegrationTests extends JavaIntegrationTests {
@Autowired CouchbaseClientFactory couchbaseClientFactory;
@Autowired ReactivePersonRepository rxRepo;
@Autowired PersonRepository repo;
@Autowired CouchbaseTemplate cbTmpl;
@Autowired ReactiveCouchbaseTemplate rxCBTmpl;
@Autowired ReactiveCouchbaseTemplate operations;
// This will pick up CouchbaseTransactionalOperator
@Autowired TransactionalOperator txOperator;
String sName = "_default";
String cName = "_default";
Person WalterWhite;
@BeforeAll
public static void beforeAll() {
callSuperBeforeAll(new Object() {});
}
@AfterAll
public static void afterAll() {
callSuperAfterAll(new Object() {});
}
@BeforeEach
public void beforeEachTest() {
assertTrue(txOperator instanceof CouchbaseTransactionalOperator);
WalterWhite = new Person("Walter", "White");
TransactionTestUtil.assertNotInTransaction();
TransactionTestUtil.assertNotInTransaction();
List<RemoveResult> rp0 = cbTmpl.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).all();
List<RemoveResult> rp1 = cbTmpl.removeByQuery(Person.class).withConsistency(REQUEST_PLUS).inScope(sName)
.inCollection(cName).all();
List<Person> p0 = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).all();
List<Person> p1 = cbTmpl.findByQuery(Person.class).withConsistency(REQUEST_PLUS).inScope(sName).inCollection(cName)
.all();
}
@Test
public void replacePersonTemplate() {
Person person = rxCBTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
Flux<Person> result = txOperator.execute((ctx) -> rxCBTmpl.findById(Person.class).one(person.id())
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt"))));
result.blockLast();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt");
}
@Test
public void replacePersonRbTemplate() {
Person person = rxCBTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
Flux<Person> result = txOperator.execute((ctx) -> rxCBTmpl.findById(Person.class).one(person.id())
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))
.map(it -> throwSimulateFailureException(it)));
assertThrowsWithCause(result::blockLast, TransactionSystemUnambiguousException.class,
SimulateFailureException.class);
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person, pFound, "Should have found " + person);
}
@Test
public void insertPersonTemplate() {
Person person = WalterWhite;
Flux<Person> result = txOperator.execute((ctx) -> rxCBTmpl.insertById(Person.class).one(person)
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt"))));
result.blockLast();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt");
}
@Test
public void insertPersonRbTemplate() {
Person person = WalterWhite;
Flux<Person> result = txOperator.execute((ctx) -> rxCBTmpl.insertById(Person.class).one(person)
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))
.map(it -> throwSimulateFailureException(it)));
assertThrowsWithCause(result::blockLast, TransactionSystemUnambiguousException.class,
SimulateFailureException.class);
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertNull(pFound, "Should NOT have found " + pFound);
}
@Test
public void replacePersonRbRepo() {
Person person = rxCBTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
Flux<Person> result = txOperator.execute((ctx) -> rxRepo.withCollection(cName).findById(person.id())
.flatMap(p -> rxRepo.withCollection(cName).save(p.withFirstName("Walt")))
.flatMap(it -> Mono.error(new SimulateFailureException())));
assertThrowsWithCause(result::blockLast, TransactionSystemUnambiguousException.class,
SimulateFailureException.class);
Person pFound = rxRepo.withCollection(cName).findById(person.id()).block();
assertEquals(person, pFound, "Should have found " + person);
}
@Test
public void insertPersonRbRepo() {
Person person = WalterWhite;
Flux<Person> result = txOperator.execute((ctx) -> rxRepo.withCollection(cName).save(person) // insert
.map(it -> throwSimulateFailureException(it)));
assertThrowsWithCause(result::blockLast, TransactionSystemUnambiguousException.class,
SimulateFailureException.class);
Person pFound = rxRepo.withCollection(cName).findById(person.id()).block();
assertNull(pFound, "Should NOT have found " + pFound);
}
@Test
public void insertPersonRepo() {
Person person = WalterWhite;
Flux<Person> result = txOperator.execute((ctx) -> rxRepo.withCollection(cName).save(person) // insert
.flatMap(p -> rxRepo.withCollection(cName).save(p.withFirstName("Walt"))));
result.blockLast();
Person pFound = rxRepo.withCollection(cName).findById(person.id()).block();
assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt");
}
@Test
public void replacePersonSpringTransactional() {
Person person = WalterWhite;
rxCBTmpl.insertById(Person.class).inCollection(cName).one(person).block();
Mono<?> result = rxCBTmpl.findById(Person.class).one(person.id())
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt"))).as(txOperator::transactional);
result.block();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person.withFirstName("Walt"), pFound, "Should have found " + person);
}
@Test
public void replacePersonRbSpringTransactional() {
Person person = rxCBTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
Mono<?> result = rxCBTmpl.findById(Person.class).one(person.id())
.flatMap(p -> rxCBTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))
.flatMap(it -> Mono.error(new SimulateFailureException())).as(txOperator::transactional);
assertThrowsWithCause(result::block, TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person, pFound, "Should have found " + person);
assertEquals(person.getFirstname(), pFound.getFirstname(), "firstname should be " + person.getFirstname());
}
@Test
public void findReplacePersonCBTransactionsRxTmpl() {
Person person = rxCBTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
Flux<Person> result = txOperator.execute(ctx -> rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id())
.flatMap(pGet -> rxCBTmpl.replaceById(Person.class).inCollection(cName).one(pGet.withFirstName("Walt"))));
result.blockLast();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person.withFirstName("Walt"), pFound, "Should have found Walt");
}
@Test
public void insertReplacePersonsCBTransactionsRxTmpl() {
Person person = WalterWhite;
Flux<Person> result = txOperator.execute((ctx) -> rxCBTmpl.insertById(Person.class).inCollection(cName).one(person)
.flatMap(pInsert -> rxCBTmpl.replaceById(Person.class).inCollection(cName).one(pInsert.withFirstName("Walt"))));
result.blockLast();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person.withFirstName("Walt"), pFound, "Should have found Walt");
}
@Test
void transactionalSavePerson() {
Person person = WalterWhite;
savePerson(person).block();
Person pFound = rxCBTmpl.findById(Person.class).inCollection(cName).one(person.id()).block();
assertEquals(person, pFound, "Should have found " + person);
}
public Mono<Person> savePerson(Person person) {
return operations.save(person) //
.as(txOperator::transactional);
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2022 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.transactions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.ReactivePersonRepository;
import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator;
import org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException;
import org.springframework.data.couchbase.transactions.util.TransactionTestUtil;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.reactive.TransactionalOperator;
/**
* Tests for com.couchbase.transactions without using the spring data transactions framework
* <p>
* Tests CouchbaseTransactionalOperator.
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
@SpringJUnitConfig(TransactionsConfig.class)
// I think these are all redundant (see CouchbaseReactiveTransactionNativeTests). There does not seem to be a blocking
// form of TransactionalOperator. Also there does not seem to be a need for a CouchbaseTransactionalOperator as
// TransactionalOperator.create(reactiveCouchbaseTransactionManager) seems to work just fine. (I don't recall what
// merits the "Native" in the name).
public class CouchbaseTransactionNativeIntegrationTests extends JavaIntegrationTests {
@Autowired CouchbaseClientFactory couchbaseClientFactory;
@Autowired TransactionManager couchbaseTransactionManager;
@Autowired PersonRepository repo;
@Autowired ReactivePersonRepository repoRx;
@Autowired CouchbaseTemplate cbTmpl;
@Autowired ReactiveCouchbaseTemplate rxCbTmpl;
@Autowired TransactionalOperator txOperator;
static String cName; // short name
Person WalterWhite;
@BeforeAll
public static void beforeAll() {
callSuperBeforeAll(new Object() {});
// short names
cName = null;// cName;
}
@AfterAll
public static void afterAll() {
callSuperAfterAll(new Object() {});
}
@BeforeEach
public void beforeEach() {
assertTrue(txOperator instanceof CouchbaseTransactionalOperator);
WalterWhite = new Person("Walter", "White");
TransactionTestUtil.assertNotInTransaction();
}
@AfterEach
public void afterEach() {
TransactionTestUtil.assertNotInTransaction();
}
@Test
public void replacePersonTemplate() {
Person person = cbTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite);
assertThrowsWithCause(() -> txOperator.execute((ctx) -> rxCbTmpl.findById(Person.class).one(person.id()) //
.flatMap(pp -> rxCbTmpl.replaceById(Person.class).one(pp.withIdFirstname()) //
.map(ppp -> throwSimulateFailureException(ppp))))
.blockLast(), TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString());
assertEquals(person.getFirstname(), pFound.getFirstname(), "firstname should be " + person.getFirstname());
}
@Test
public void replacePersonRbTemplate() {
Person person = cbTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite);
assertThrowsWithCause(
() -> txOperator.execute((ctx) -> rxCbTmpl.findById(Person.class).one(person.getId().toString()) //
.flatMap(p -> rxCbTmpl.replaceById(Person.class).one(p.withIdFirstname())) //
.map(ppp -> throwSimulateFailureException(ppp))).blockLast(), //
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.getId().toString());
assertEquals(person.getFirstname(), pFound.getFirstname(), "firstname should be " + person.getFirstname());
}
@Test
public void insertPersonTemplate() {
txOperator.execute((ctx) -> rxCbTmpl.insertById(Person.class).one(WalterWhite)
.flatMap(p -> rxCbTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))).blockLast();
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(WalterWhite.id());
assertEquals("Walt", pFound.getFirstname(), "firstname should be Walt");
}
@Test
public void insertPersonRbTemplate() {
assertThrowsWithCause(
() -> txOperator.execute((ctx) -> rxCbTmpl.insertById(Person.class).one(WalterWhite)
.flatMap(p -> rxCbTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))
.map(it -> throwSimulateFailureException(it))).blockLast(),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(WalterWhite.id());
assertNull(pFound, "Should NOT have found " + pFound);
}
@Test
public void replacePersonRbRepo() {
Person person = repo.withCollection(cName).save(WalterWhite);
assertThrowsWithCause(() -> txOperator.execute(ctx -> {
return repoRx.withCollection(cName).findById(person.id())
.flatMap(p -> repoRx.withCollection(cName).save(p.withFirstName("Walt")))
.map(pp -> throwSimulateFailureException(pp));
}).blockLast(), TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.id());
assertEquals(person, pFound, "Should have found " + person);
}
@Test
public void insertPersonRbRepo() {
assertThrowsWithCause(() -> txOperator.execute((ctx) -> repoRx.withCollection(cName).save(WalterWhite) // insert
.flatMap(p -> repoRx.withCollection(cName).save(p.withFirstName("Walt"))) // replace
.map(it -> throwSimulateFailureException(it))).blockLast(), TransactionSystemUnambiguousException.class,
SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(WalterWhite.id());
assertNull(pFound, "Should NOT have found " + pFound);
}
@Test
public void insertPersonRepo() {
txOperator.execute((ctx) -> repoRx.withCollection(cName).save(WalterWhite) // insert
.flatMap(p -> repoRx.withCollection(cName).save(p.withFirstName("Walt"))) // replace
).blockFirst();
Optional<Person> pFound = repo.withCollection(cName).findById(WalterWhite.id());
assertEquals("Walt", pFound.get().getFirstname(), "firstname should be Walt");
}
@Test
public void replacePersonRbSpringTransactional() {
Person person = cbTmpl.insertById(Person.class).inCollection(cName).one(WalterWhite);
assertThrowsWithCause(
() -> txOperator.execute((ctx) -> rxCbTmpl.findById(Person.class).one(person.getId().toString())
.flatMap(p -> rxCbTmpl.replaceById(Person.class).one(p.withFirstName("Walt")))
.map(it -> throwSimulateFailureException(it))).blockLast(),
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
Person pFound = cbTmpl.findById(Person.class).inCollection(cName).one(person.id());
assertEquals(person.getFirstname(), pFound.getFirstname(), "firstname should be Walter");
}
}

Some files were not shown because too many files have changed in this diff Show More