Scopes and collections for repositories (#1149)

* Add support for scopes and collections for repositories.

Adds DynamicProxyable and DynamicInvocationHandler to
set scope/collection/options on PseudoArgs when calling
operations via repository interfaces.

Closes #963.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2021-08-09 09:57:29 -07:00
committed by GitHub
parent 9621c4960a
commit 7cde6b919e
97 changed files with 2617 additions and 962 deletions

View File

@@ -31,7 +31,6 @@ import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;
@@ -50,6 +49,7 @@ import org.springframework.data.couchbase.util.JavaIntegrationTests;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.query.QueryScanConsistency;
;
@@ -121,7 +121,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
returned = (User) operator.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
@@ -259,7 +259,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;

View File

@@ -36,6 +36,7 @@ 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.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;
@@ -44,18 +45,19 @@ 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.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.UnambiguousTimeoutException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
@@ -752,4 +754,51 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
.inCollection(otherCollection).withOptions(options).one(vie));
}
@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(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
assertEquals(0, notfound.size(), "should not 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(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
assertEquals(0, notfound.size(), "should not 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) {}
}
}
}

View File

@@ -16,7 +16,8 @@
package org.springframework.data.couchbase.core;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.time.Duration;
import java.util.UUID;

View File

@@ -150,7 +150,8 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
}
// if replace or remove, we need to insert a document to replace
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById || operator instanceof ExecutableRemoveById) {
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById
|| operator instanceof ExecutableRemoveById) {
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update

View File

@@ -19,8 +19,8 @@ package org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Version;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
@@ -42,7 +42,6 @@ public class Airport extends ComparableEntity {
@CreatedBy private String createdBy;
@PersistenceConstructor
public Airport(String id, String iata, String icao) {
this.id = id;
@@ -78,6 +77,7 @@ public class Airport extends ComparableEntity {
version = Long.valueOf(0);
return this;
}
public String getCreatedBy() {
return createdBy;
}

View File

@@ -16,18 +16,33 @@
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.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -41,8 +56,11 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
// @Scope("repositoryScope")
// @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> {
// override an annotate with REQUEST_PLUS
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAll();
@@ -51,11 +69,16 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
List<Airport> findAllByIata(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);
// 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);
@@ -97,4 +120,42 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Optional<Airport> findByIdAndIata(String id, String iata);
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
// @Meta
@Scope
@Collection
@ScanConsistency
@Expiry
@Options
public @interface ComposedMetaAnnotation {
// @AliasFor(annotation = Meta.class, attribute = "maxExecutionTimeMs")
// long execTime() default -1;
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = ScanConsistency.class, attribute = "analytics")
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = Scope.class, attribute = "value")
String scope() default DEFAULT_SCOPE;
@AliasFor(annotation = Collection.class, attribute = "value")
String collection() default DEFAULT_COLLECTION;
@AliasFor(annotation = Expiry.class, attribute = "expiry")
int expiry() default 0;
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
TimeUnit expiryUnit() default TimeUnit.SECONDS;
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
String expiryExpression() default "";
@AliasFor(annotation = Options.class, attribute = "timeoutMs")
long timeoutMs() default 0;
}
}

View File

@@ -38,6 +38,7 @@ 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;
@@ -52,8 +53,10 @@ import com.couchbase.client.java.json.JacksonTransformers;
*/
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseAuditing(auditorAwareRef="reactiveAuditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
@EnableReactiveCouchbaseAuditing(auditorAwareRef = "reactiveAuditorAwareRef",
dateTimeProviderRef = "dateTimeProviderRef")
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";

View File

@@ -16,7 +16,6 @@
package org.springframework.data.couchbase.domain;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.couchbase.repository.Query;
@@ -92,7 +91,7 @@ public interface PersonRepository extends CrudRepository<Person, String> {
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
Optional<Person> findById(UUID var1);
Person findById(UUID var1);
boolean existsById(UUID var1);

View File

@@ -16,19 +16,19 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.core.RemoveResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
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.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
@@ -41,7 +41,8 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> {
public interface ReactiveAirportRepository
extends ReactiveCouchbaseRepository<Airport, String>, DynamicProxyable<ReactiveAirportRepository> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@@ -57,6 +58,9 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIata(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);

View File

@@ -15,9 +15,9 @@
*/
package org.springframework.data.couchbase.domain;
import org.springframework.data.domain.ReactiveAuditorAware;
import reactor.core.publisher.Mono;
import org.springframework.data.domain.ReactiveAuditorAware;
/**
* This class returns a string that represents the current user
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
public static final String AUDITOR = "reactive_auditor";
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just(AUDITOR);

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface ReactiveUserColRepository
extends ReactiveCouchbaseRepository<UserCol, String>, DynamicProxyable<ReactiveUserColRepository> {
<S extends UserCol> Mono<S> save(S var1);
Flux<UserCol> findByFirstname(String firstname);
Flux<UserCol> findByFirstnameIn(String... firstnames);
Flux<UserCol> findByFirstnameIn(JsonArray firstnames);
Flux<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
Flux<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
Flux<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
Flux<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
Flux<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Scope;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
@Scope("other_scope")
@Collection("other_collection")
public class UserCol extends User {
@PersistenceConstructor
public UserCol(final String id, final String firstname, final String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.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> {
<S extends UserCol> S save(S var1);
List<UserCol> findByFirstname(String firstname);
List<UserCol> findByFirstnameIn(String... firstnames);
List<UserCol> findByFirstnameIn(JsonArray firstnames);
List<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -20,10 +20,12 @@ import java.util.List;
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;
/**
* User Repository for tests
@@ -32,6 +34,7 @@ import com.couchbase.client.java.json.JsonArray;
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByFirstname(String firstname);
@@ -51,4 +54,5 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* 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.
@@ -21,10 +21,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -41,21 +43,22 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.Iata;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserAnnotated;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
@@ -73,8 +76,14 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.core.error.IndexExistsException;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.kv.MutationState;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -96,6 +105,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Autowired CouchbaseTemplate couchbaseTemplate;
String scopeName = "_default";
String collectionName = "_default";
@BeforeEach
public void beforeEach() {
try {
@@ -182,12 +194,17 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
vie = new Airport("airports::vie", "vie", "low6");
vie = airportRepository.save(vie);
Airport airport2 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.findByIata(vie.getIata());
assertEquals(airport2, vie);
List<Airport> airports = airportRepository.findAllByIata("vie");
assertEquals(1, airports.size());
Airport airport1 = airportRepository.findById(airports.get(0).getId()).get();
assertEquals(airport1.getIata(), vie.getIata());
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport1.getId(), vie.getId());
airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport2.getId(), vie.getId());
} finally {
airportRepository.delete(vie);
}
@@ -201,7 +218,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
vie = airportRepository.save(vie);
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS)
.matching(new Query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport"))).all();
.matching(org.springframework.data.couchbase.core.query.Query
.query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport")))
.all();
assertFalse(airports.isEmpty(), "should have found aiport");
} finally {
airportRepository.delete(vie);
@@ -214,18 +233,132 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = airportRepository.save(vie);
Airport airport2 = airportRepository.findByIata(Iata.vie);
Airport airport2 = airportRepository.findByIata(vie.getIata());
assertNotNull(airport2, "should have found " + vie);
assertEquals(airport2.getId(), vie.getId());
} finally {
airportRepository.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", "low7");
try {
Airport saved = airportRepository.withScope(scopeName).withCollection(collectionName).save(vie);
// given collection (on scope used by template)
Airport airport2 = airportRepository.withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport2);
// given scope and collection
Airport airport3 = airportRepository.withScope(scopeName).withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport3);
// given bad collection
assertThrows(IndexFailureException.class,
() -> airportRepository.withCollection("bogusCollection").iata(vie.getIata()));
// given bad scope
assertThrows(IndexFailureException.class, () -> airportRepository.withScope("bogusScope").iata(vie.getIata()));
} finally {
airportRepository.delete(vie);
}
}
@Test
@IgnoreWhen(hasCapabilities = { Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollectionFail() {
// can test against _default._default without setting up additional scope/collection
// the server will throw an exception if it doesn't support COLLECTIONS
Airport vie = new Airport("airports::vie", "vie", "low8");
try {
Airport saved = airportRepository.save(vie);
assertThrows(CouchbaseException.class, () -> airportRepository.withScope("non_default_scope_name")
.withCollection(collectionName).iata(vie.getIata()));
} finally {
airportRepository.delete(vie);
}
}
@Test
void findBySimplePropertyWithOptions() {
Airport vie = new Airport("airports::vie", "vie", "low9");
JsonArray positionalParams = JsonArray.create().add("this parameter will be overridden");
// JsonObject namedParams = JsonObject.create().put("$1", vie.getIata());
try {
Airport saved = airportRepository.save(vie);
// Duration of 1 nano-second will cause timeout
assertThrows(AmbiguousTimeoutException.class, () -> airportRepository
.withOptions(QueryOptions.queryOptions().timeout(Duration.ofNanos(1))).iata(vie.getIata()));
Airport airport3 = airportRepository.withOptions(
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata());
assertEquals(saved, airport3);
} finally {
airportRepository.delete(vie);
}
}
@Test
public void saveNotBounded() {
// save() followed by query with NOT_BOUNDED will result in not finding the document
Airport vie = new Airport("airports::vie", "vie", "low9");
Airport airport2 = null;
for (int i = 1; i <= 100; i++) {
// set version == 0 so save() will be an upsert, not a replace
Airport saved = airportRepository.save(vie.clearVersion());
try {
airport2 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.NOT_BOUNDED))
.iata(saved.getIata());
if (airport2 == null) {
break;
}
} catch (DataRetrievalFailureException drfe) {
airport2 = null; //
} finally {
// airportRepository.delete(vie);
// instead of delete, use removeResult to test QueryOptions.consistentWith()
RemoveResult removeResult = couchbaseTemplate.removeById().one(vie.getId());
assertEquals(vie.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
Airport airport3 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS)
.consistentWith(MutationState.from(removeResult.getMutationToken().get())))
.iata(vie.getIata());
assertNull(airport3, "should have been removed");
}
}
assertNull(airport2, "airport2 should have likely been null at least once");
}
@Test
public void testCas() {
User user = new User("1", "Dave", "Wilson");
userRepository.save(user);
userRepository.findByFirstname("Dave");
user.setVersion(user.getVersion() - 1);
assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user));
user.setVersion(0);
@@ -233,12 +366,20 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
userRepository.delete(user);
}
@Test
public void testExpiryAnnotation() {
UserAnnotated user = new UserAnnotated("1", "Dave", "Wilson");
userRepository.save(user);
userRepository.findByFirstname("Dave");
sleep(2000);
assertThrows(DataRetrievalFailureException.class, () -> userRepository.delete(user));
}
@Test
void count() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
try {
airportRepository.saveAll(
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
@@ -385,12 +526,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
Airport frankfurt = new Airport("airports::fra", "fra", "EDDF");
Airport losAngeles = new Airport("airports::lax", "lax", "KLAX");
try {
airportRepository.saveAll(asList(vienna, frankfurt, losAngeles));
airportRepository.deleteAllById(asList(vienna.getId(), losAngeles.getId()));
assertThat(airportRepository.findAll()).containsExactly(frankfurt);
} finally {
airportRepository.deleteAll();
@@ -430,7 +568,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order
} catch (InterruptedException ie) {}
} catch (InterruptedException ie) {
;
}
}
@Configuration
@@ -463,10 +603,16 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
return new NaiveAuditorAware();
}
@Override
public void configureEnvironment(final ClusterEnvironment.Builder builder) {
builder.ioConfig().maxHttpConnections(11).idleHttpConnectionTimeout(Duration.ofSeconds(4));
return;
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
}
}

View File

@@ -73,7 +73,8 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
Airport saved = airportRepository.save(vie).block();
Airport airport1 = airportRepository.findById(saved.getId()).block();
assertEquals(airport1, saved);
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide this
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide
// this
} finally {
airportRepository.delete(vie).block();
}

View File

@@ -130,17 +130,17 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
Airport vie = new Airport("airports::vie", "vie", "low3");
Airport saved1 = airportRepository.save(vie).block();
Airport saved2 = airportRepository.save(vie.withId(UUID.randomUUID().toString())).block();
try {
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
System.out.println("------------------------------");
System.out.println(airport.block());
System.out.println("------------------------------");
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
System.out.println(airports.collectList().block());
System.out.println("------------------------------");
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
System.out.println(ap.block());
try {
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
System.out.println("------------------------------");
System.out.println(airport.block());
System.out.println("------------------------------");
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
System.out.println(airports.collectList().block());
System.out.println("------------------------------");
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
System.out.println(ap.block());
} finally {
airportRepository.delete(saved1).block();
airportRepository.delete(saved2).block();
@@ -249,6 +249,22 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
}
}
@Test
void deleteOne() {
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
try {
Airport ap = airportRepository.save(vienna).block();
assertEquals(vienna.getId(), ap.getId(), "should have saved what was provided");
airportRepository.delete(vienna).as(StepVerifier::create).verifyComplete();
airportRepository.findAll().as(StepVerifier::create).verifyComplete();
} finally {
airportRepository.deleteAll().block();
}
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {

View File

@@ -0,0 +1,211 @@
/*
* 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.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.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.Config;
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.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
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;
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired AirportRepository airportRepository;
@Autowired UserColRepository userColRepository;
@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();
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// seems that @Autowired is not adequate, so ...
airportRepository = (AirportRepository) ac.getBean("airportRepository");
userColRepository = (UserColRepository) ac.getBean("userColRepository");
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
// no-op
// then call the super method
super.afterEach();
}
@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(QueryScanConsistency.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(QueryScanConsistency.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(QueryScanConsistency.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(CollectionIdentifier.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) {}
}
}
// 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(CollectionIdentifier.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) {}
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* 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.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.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
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 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;
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired ReactiveAirportRepository airportRepository;
@Autowired ReactiveUserColRepository userColRepository;
@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();
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// seems that @Autowired is not adequate, so ...
airportRepository = (ReactiveAirportRepository) ac.getBean("reactiveAirportRepository");
userColRepository = (ReactiveUserColRepository) ac.getBean("reactiveUserColRepository");
}
@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 = airportRepository.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 = airportRepository.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 = airportRepository.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) {}
}
}
}