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:
@@ -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;
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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,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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user