71
src/test/java/org/springframework/data/couchbase/cache/CacheUser.java
vendored
Normal file
71
src/test/java/org/springframework/data/couchbase/cache/CacheUser.java
vendored
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
103
src/test/java/org/springframework/data/couchbase/cache/CouchbaseCacheCollectionIntegrationTests.java
vendored
Normal file
103
src/test/java/org/springframework/data/couchbase/cache/CouchbaseCacheCollectionIntegrationTests.java
vendored
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
137
src/test/java/org/springframework/data/couchbase/cache/CouchbaseCacheIntegrationTests.java
vendored
Normal file
137
src/test/java/org/springframework/data/couchbase/cache/CouchbaseCacheIntegrationTests.java
vendored
Normal 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() {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user