Scope and collection API for template. (#1133)

Scope and Collection API for template.

Closes #963.
Original pull request: #1071.
This commit is contained in:
Michael Reiche
2021-06-02 09:38:02 -04:00
committed by mikereiche
parent 0930e5b5fe
commit 42b7fb3af8
92 changed files with 4630 additions and 749 deletions

28
pom.xml
View File

@@ -25,16 +25,16 @@
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
@@ -88,7 +88,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.1.0.RELEASE</version>
<version>3.4.6</version>
<scope>test</scope>
</dependency>
@@ -170,12 +170,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>

View File

@@ -28,6 +28,7 @@ import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIn
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
@@ -60,14 +61,14 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
this.converter = converter;
this.templateSupport = new CouchbaseTemplateSupport(converter, translationService);
this.reactiveCouchbaseTemplate = new ReactiveCouchbaseTemplate(clientFactory, converter, translationService);
this.mappingContext = this.converter.getMappingContext();
if (mappingContext instanceof CouchbaseMappingContext) {
CouchbaseMappingContext cmc = (CouchbaseMappingContext) mappingContext;
if (cmc.isAutoIndexCreation()) {
indexCreator = new CouchbasePersistentEntityIndexCreator(cmc, this);
}
}
if (mappingContext instanceof CouchbaseMappingContext) {
CouchbaseMappingContext cmc = (CouchbaseMappingContext) mappingContext;
if (cmc.isAutoIndexCreation()) {
indexCreator = new CouchbasePersistentEntityIndexCreator(cmc, this);
}
}
}
@Override
@@ -184,4 +185,5 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
TemplateSupport support() {
return templateSupport;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,19 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.Map;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllExists;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithExistsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
/**
* Insert Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableExistsByIdOperation {
/**
@@ -28,6 +38,9 @@ public interface ExecutableExistsByIdOperation {
*/
ExecutableExistsById existsById();
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingExistsById extends OneAndAllExists {
/**
@@ -36,6 +49,7 @@ public interface ExecutableExistsByIdOperation {
* @param id the ID to perform the operation on.
* @return true if the document exists, false otherwise.
*/
@Override
boolean one(String id);
/**
@@ -44,20 +58,59 @@ public interface ExecutableExistsByIdOperation {
* @param ids the ids to check.
* @return a map consisting of the document IDs as the keys and if they exist as the value.
*/
@Override
Map<String, Boolean> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById, WithCollection {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface ExistsByIdWithOptions<T> extends TerminatingExistsById, WithExistsOptions<T> {
/**
* Allows to specify a different collection than the default one configured.
* Fluent method to specify options to use for execution
*
* @param collection the collection to use in this scope.
* @param options options to use for execution
*/
TerminatingExistsById inCollection(String collection);
@Override
TerminatingExistsById withOptions(ExistsOptions options);
}
interface ExecutableExistsById extends ExistsByIdWithCollection {}
/**
*
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface ExistsByIdInCollection<T> extends ExistsByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
ExistsByIdWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface ExistsByIdInScope<T> extends ExistsByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
ExistsByIdInCollection<T> inScope(String scope);
}
/**
* Provides methods for constructing KV exists operations in a fluent way.
*/
interface ExecutableExistsById extends ExistsByIdInScope {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,14 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport;
import java.util.Collection;
import java.util.Map;
import org.springframework.data.couchbase.core.ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.ExistsOptions;
public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByIdOperation {
private final CouchbaseTemplate template;
@@ -32,17 +33,25 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
@Override
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdSupport(template, null);
return new ExecutableExistsByIdSupport(template, null, null, null);
}
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
private final CouchbaseTemplate template;
private final String scope;
private final String collection;
private final ExistsOptions options;
private final ReactiveExistsByIdSupport reactiveSupport;
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String collection) {
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
final ExistsOptions options) {
this.template = template;
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), collection);
this.scope = scope;
this.collection = collection;
this.options = options;
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), scope, collection, options);
}
@Override
@@ -56,11 +65,22 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
}
@Override
public TerminatingExistsById inCollection(final String collection) {
public ExistsByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, collection);
return new ExecutableExistsByIdSupport(template, scope, collection, options);
}
@Override
public TerminatingExistsById withOptions(final ExistsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableExistsByIdSupport(template, scope, collection, options);
}
@Override
public ExistsByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, scope, collection, options);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,14 +21,23 @@ import java.util.stream.Stream;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAll;
import org.springframework.data.couchbase.core.support.WithAnalyticsConsistency;
import org.springframework.data.couchbase.core.support.WithAnalyticsOptions;
import org.springframework.data.couchbase.core.support.WithAnalyticsQuery;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
public interface ExecutableFindByAnalyticsOperation {
/**
* FindByAnalytics Operations
*
* @author Christoph Strobl
* @since 2.0
*/public interface ExecutableFindByAnalyticsOperation {
/**
* Queries the analytics service.
@@ -117,8 +126,53 @@ public interface ExecutableFindByAnalyticsOperation {
}
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use.
*/
interface FindByAnalyticsWithOptions<T> extends FindByAnalyticsWithQuery<T>, WithAnalyticsOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
FindByAnalyticsWithQuery<T> withOptions(AnalyticsOptions options);
}
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindByAnalyticsInCollection<T> extends FindByAnalyticsWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindByAnalyticsWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindByAnalyticsInScope<T> extends FindByAnalyticsInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindByAnalyticsInCollection<T> inScope(String scope);
}
@Deprecated
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsWithQuery<T> {
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsInScope<T> {
/**
* Allows to override the default scan consistency.
@@ -138,9 +192,24 @@ public interface ExecutableFindByAnalyticsOperation {
* @param scanConsistency the custom scan consistency to use for this analytics query.
*/
FindByAnalyticsConsistentWith<T> withConsistency(AnalyticsScanConsistency scanConsistency);
}
interface ExecutableFindByAnalytics<T> extends FindByAnalyticsWithConsistency<T> {}
/**
* Result type override (Optional).
*/
interface FindByAnalyticsWithProjection<T> extends FindByAnalyticsWithConsistency<T> {
/**
* Define the target type fields should be mapped to. <br />
* Skip this step if you are anyway only interested in the original domain type.
*
* @param returnType must not be {@literal null}.
* @return new instance of {@link FindByAnalyticsWithConsistency}.
* @throws IllegalArgumentException if returnType is {@literal null}.
*/
<R> FindByAnalyticsWithConsistency<R> as(Class<R> returnType);
}
interface ExecutableFindByAnalytics<T> extends FindByAnalyticsWithProjection<T> {}
}

View File

@@ -20,7 +20,9 @@ import java.util.stream.Stream;
import org.springframework.data.couchbase.core.ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.util.Assert;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation {
@@ -35,26 +37,34 @@ public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFind
@Override
public <T> ExecutableFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, ALL_QUERY,
AnalyticsScanConsistency.NOT_BOUNDED);
return new ExecutableFindByAnalyticsSupport<>(template, domainType, domainType, ALL_QUERY, null, null, null, null);
}
static class ExecutableFindByAnalyticsSupport<T> implements ExecutableFindByAnalytics<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final Class<?> domainType;
private final Class<T> returnType;
private final ReactiveFindByAnalyticsSupport<T> reactiveSupport;
private final AnalyticsQuery query;
private final AnalyticsScanConsistency scanConsistency;
private final String scope;
private final String collection;
private final AnalyticsOptions options;
ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency) {
ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class<?> domainType,
final Class<T> returnType, final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency,
final String scope, final String collection, final AnalyticsOptions options) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = query;
this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, query,
scanConsistency, new NonReactiveSupportWrapper(template.support()));
this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, returnType, query,
scanConsistency, scope, collection, options, new NonReactiveSupportWrapper(template.support()));
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
}
@Override
@@ -74,18 +84,49 @@ public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFind
@Override
public TerminatingFindByAnalytics<T> matching(final AnalyticsQuery query) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public FindByAnalyticsWithQuery<T> withOptions(final AnalyticsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public FindByAnalyticsInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
@Deprecated
public FindByAnalyticsWithQuery<T> consistentWith(final AnalyticsScanConsistency scanConsistency) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public FindByAnalyticsWithConsistency<T> withConsistency(final AnalyticsScanConsistency scanConsistency) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public <R> FindByAnalyticsWithConsistency<R> as(final Class<R> returnType) {
Assert.notNull(returnType, "returnType must not be null!");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,19 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.OneAndAllId;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.WithGetOptions;
import org.springframework.data.couchbase.core.support.WithProjectionId;
import org.springframework.data.couchbase.core.support.InScope;
import com.couchbase.client.java.kv.GetOptions;
/**
* Get Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableFindByIdOperation {
/**
@@ -30,6 +40,11 @@ public interface ExecutableFindByIdOperation {
*/
<T> ExecutableFindById<T> findById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*
* @param <T> the entity type to use for the results.
*/
interface TerminatingFindById<T> extends OneAndAllId<T> {
/**
@@ -50,28 +65,66 @@ public interface ExecutableFindByIdOperation {
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T>, WithCollection<T> {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdWithOptions<T> extends TerminatingFindById<T>, WithGetOptions<T> {
/**
* Allows to specify a different collection than the default one configured.
* Fluent method to specify options to use for execution
*
* @param collection the collection to use in this scope.
* @param options options to use for execution
*/
TerminatingFindById<T> inCollection(String collection);
@Override
TerminatingFindById<T> withOptions(GetOptions options);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T>, WithProjectionId<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdInCollection<T> extends FindByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindByIdWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdInScope<T> extends FindByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindByIdInCollection<T> inScope(String scope);
}
interface FindByIdWithProjection<T> extends FindByIdInScope<T>, WithProjectionId<T> {
/**
* Load only certain fields for the document.
*
* @param fields the projected fields to load.
*/
FindByIdWithCollection<T> project(String... fields);
@Override
FindByIdInScope<T> project(String... fields);
}
/**
* Provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ExecutableFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -22,6 +22,8 @@ import java.util.List;
import org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.GetOptions;
public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation {
private final CouchbaseTemplate template;
@@ -32,23 +34,29 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
@Override
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
return new ExecutableFindByIdSupport<>(template, domainType, null, null);
return new ExecutableFindByIdSupport<>(template, domainType, null, null, null, null);
}
static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final GetOptions options;
private final List<String> fields;
private final ReactiveFindByIdSupport<T> reactiveSupport;
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String collection, List<String> fields) {
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String scope, String collection,
GetOptions options, List<String> fields) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.fields = fields;
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields, new NonReactiveSupportWrapper(template.support()));
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, scope, collection, options,
fields, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -62,16 +70,29 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, fields);
public TerminatingFindById<T> withOptions(final GetOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
public FindByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
}
@Override
public FindByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
}
@Override
public FindByIdInScope<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,16 +22,24 @@ import java.util.stream.Stream;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAll;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithConsistency;
import org.springframework.data.couchbase.core.support.WithDistinct;
import org.springframework.data.couchbase.core.support.WithProjection;
import org.springframework.data.couchbase.core.support.WithQuery;
import org.springframework.data.couchbase.core.support.WithQueryOptions;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Query Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableFindByQueryOperation {
/**
@@ -41,13 +49,18 @@ public interface ExecutableFindByQueryOperation {
*/
<T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingFindByQuery<T> extends OneAndAll<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws IncorrectResultSizeDataAccessException if more than one match found.
*/
@Override
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
@@ -59,6 +72,7 @@ public interface ExecutableFindByQueryOperation {
* @throws IncorrectResultSizeDataAccessException if more than one match found.
*/
@Nullable
@Override
T oneValue();
/**
@@ -66,6 +80,7 @@ public interface ExecutableFindByQueryOperation {
*
* @return {@link Optional#empty()} if no match found.
*/
@Override
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
@@ -76,13 +91,15 @@ public interface ExecutableFindByQueryOperation {
* @return {@literal null} if no match found.
*/
@Nullable
@Override
T firstValue();
/**
* Get all matching elements.
* Get all matching documents.
*
* @return never {@literal null}.
*/
@Override
List<T> all();
/**
@@ -90,6 +107,7 @@ public interface ExecutableFindByQueryOperation {
*
* @return a {@link Stream} of results. Never {@literal null}.
*/
@Override
Stream<T> stream();
/**
@@ -97,6 +115,7 @@ public interface ExecutableFindByQueryOperation {
*
* @return total number of matching elements.
*/
@Override
long count();
/**
@@ -104,15 +123,15 @@ public interface ExecutableFindByQueryOperation {
*
* @return {@literal true} if at least one matching element exists.
*/
@Override
boolean exists();
}
/**
* Terminating operations invoking the actual query execution.
* Fluent methods to specify the query
*
* @author Christoph Strobl
* @since 2.0
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T>, WithQuery<T> {
@@ -122,6 +141,7 @@ public interface ExecutableFindByQueryOperation {
* @param query must not be {@literal null}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
@Override
TerminatingFindByQuery<T> matching(Query query);
/**
@@ -131,25 +151,65 @@ public interface ExecutableFindByQueryOperation {
* @return new instance of {@link ExecutableFindByQuery}.
* @throws IllegalArgumentException if criteria is {@literal null}.
*/
@Override
default TerminatingFindByQuery<T> matching(QueryCriteriaDefinition criteria) {
return matching(Query.query(criteria));
}
}
interface FindByQueryInCollection<T> extends FindByQueryWithQuery<T>, WithCollection<T> {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithOptions<T> extends FindByQueryWithQuery<T>, WithQueryOptions<T> {
/**
* Allows to override the default scan consistency.
* Fluent method to specify options to use for execution
*
* @param collection the collection to use for this query.
* @param options to use for execution
*/
FindByQueryWithQuery<T> inCollection(String collection);
@Override
TerminatingFindByQuery<T> withOptions(QueryOptions options);
}
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryInCollection<T> extends FindByQueryWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindByQueryWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryInScope<T> extends FindByQueryInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindByQueryInCollection<T> inScope(String scope);
}
/**
* To be removed at the next major release. use WithConsistency instead
*
* @param <T> the entity type to use for the results.
*/
@Deprecated
interface FindByQueryConsistentWith<T> extends FindByQueryInCollection<T> {
interface FindByQueryConsistentWith<T> extends FindByQueryInScope<T> {
/**
* Allows to override the default scan consistency.
@@ -157,10 +217,14 @@ public interface ExecutableFindByQueryOperation {
* @param scanConsistency the custom scan consistency to use for this query.
*/
@Deprecated
FindByQueryInCollection<T> consistentWith(QueryScanConsistency scanConsistency);
FindByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
}
/**
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithConsistency<T> extends FindByQueryConsistentWith<T>, WithConsistency<T> {
/**
@@ -168,18 +232,20 @@ public interface ExecutableFindByQueryOperation {
*
* @param scanConsistency the custom scan consistency to use for this query.
*/
@Override
FindByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
}
/**
* Result type override (Optional).
* Fluent method to specify a return type different than the the entity type to use for the results.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithProjection<T> extends FindByQueryWithConsistency<T> {
/**
* Define the target type fields should be mapped to. <br />
* Skip this step if you are anyway only interested in the original domain type.
* Skip this step if you are only interested in the original the entity type to use for the results.
*
* @param returnType must not be {@literal null}.
* @return new instance of {@link FindByQueryWithProjection}.
@@ -189,7 +255,9 @@ public interface ExecutableFindByQueryOperation {
}
/**
* Distinct Find support.
* Fluent method to specify DISTINCT fields
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithDistinct<T> extends FindByQueryWithProjection<T>, WithDistinct<T> {
@@ -200,14 +268,15 @@ public interface ExecutableFindByQueryOperation {
* @return new instance of {@link ExecutableFindByQuery}.
* @throws IllegalArgumentException if field is {@literal null}.
*/
@Override
FindByQueryWithProjection<T> distinct(String[] distinctFields);
}
/**
* {@link ExecutableFindByQuery} provides methods for constructing lookup operations in a fluent way.
* Provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ExecutableFindByQuery<T> extends FindByQueryWithDistinct<T> {}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.data.couchbase.core.ReactiveFindByQueryOperationSuppo
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.util.Assert;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -42,8 +43,8 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
@Override
public <T> ExecutableFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ExecutableFindByQuerySupport<T>(template, domainType, domainType, ALL_QUERY,
QueryScanConsistency.NOT_BOUNDED, null, null);
return new ExecutableFindByQuerySupport<T>(template, domainType, domainType, ALL_QUERY, null, null, null, null,
null);
}
static class ExecutableFindByQuerySupport<T> implements ExecutableFindByQuery<T> {
@@ -54,20 +55,24 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
private final Query query;
private final ReactiveFindByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
private final String scope;
private final String collection;
private final QueryOptions options;
private final String[] distinctFields;
ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class<?> domainType, final Class<T> returnType,
final Query query, final QueryScanConsistency scanConsistency, final String collection,
final String[] distinctFields) {
final Query query, final QueryScanConsistency scanConsistency, final String scope, final String collection,
final QueryOptions options, final String[] distinctFields) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = query;
this.reactiveSupport = new ReactiveFindByQuerySupport<T>(template.reactive(), domainType, returnType, query,
scanConsistency, collection, distinctFields, new NonReactiveSupportWrapper(template.support()));
scanConsistency, scope, collection, options, distinctFields, new NonReactiveSupportWrapper(template.support()));
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
this.distinctFields = distinctFields;
}
@@ -94,42 +99,35 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
} else {
scanCons = scanConsistency;
}
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanCons, collection,
distinctFields);
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanCons, scope, collection,
options, distinctFields);
}
@Override
@Deprecated
public FindByQueryInCollection<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
public FindByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
}
@Override
public <R> FindByQueryWithConsistency<R> as(final Class<R> resturnType) {
Assert.notNull(resturnType, "returnType must not be null!");
return new ExecutableFindByQuerySupport<>(template, domainType, resturnType, query, scanConsistency, collection,
distinctFields);
public <R> FindByQueryWithConsistency<R> as(final Class<R> returnType) {
Assert.notNull(returnType, "returnType must not be null!");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryWithProjection<T> distinct(final String[] distinctFields) {
Assert.notNull(distinctFields, "distinctFields must not be null!");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
@@ -146,6 +144,28 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
public boolean exists() {
return count() > 0;
}
@Override
public TerminatingFindByQuery<T> withOptions(final QueryOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,25 +18,100 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.AnyId;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.WithGetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
/**
* Query Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableFindFromReplicasByIdOperation {
/**
* Loads a document from a replica.
*
* @param domainType the entity type to use for the results.
*/
<T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
/**
* Terminating operations invoking the actual get execution.
*/
interface TerminatingFindFromReplicasById<T> extends AnyId<T> {
/**
* Finds one document based on the given ID.
*
* @param id the document ID.
* @return the entity if found.
*/
@Override
T any(String id);
/**
* Finds a list of documents based on the given IDs.
*
* @param ids the document ID ids.
* @return the list of found entities.
*/
@Override
Collection<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T>, WithCollection<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdWithOptions<T> extends TerminatingFindFromReplicasById<T>, WithGetAnyReplicaOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options options to use for execution
*/
@Override
TerminatingFindFromReplicasById<T> withOptions(GetAnyReplicaOptions options);
}
interface ExecutableFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdInCollection<T> extends FindFromReplicasByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindFromReplicasByIdWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdInScope<T> extends FindFromReplicasByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindFromReplicasByIdInCollection<T> inScope(String scope);
}
/**
* Provides methods for constructing get operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ExecutableFindFromReplicasById<T> extends FindFromReplicasByIdInScope<T> {}
}

View File

@@ -18,6 +18,8 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import org.springframework.util.Assert;
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
@@ -30,7 +32,7 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
@Override
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, domainType, null);
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, domainType, null, null, null);
}
static class ExecutableFindFromReplicasByIdSupport<T> implements ExecutableFindFromReplicasById<T> {
@@ -38,17 +40,21 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
private final CouchbaseTemplate template;
private final Class<?> domainType;
private final Class<T> returnType;
private final String scope;
private final String collection;
private final GetAnyReplicaOptions options;
private final ReactiveFindFromReplicasByIdSupport<T> reactiveSupport;
ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class<?> domainType, Class<T> returnType,
String collection) {
String scope, String collection, GetAnyReplicaOptions options) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.returnType = returnType;
this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType,
collection, new NonReactiveSupportWrapper(template.support()));
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -62,9 +68,21 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
public TerminatingFindFromReplicasById<T> withOptions(final GetAnyReplicaOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
}
@Override
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, collection);
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
}
@Override
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,37 +18,107 @@ package org.springframework.data.couchbase.core;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithInsertOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* Insert Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableInsertByIdOperation {
/**
* Insert using the KV service.
*
* @param domainType the entity type to insert.
*/
<T> ExecutableInsertById<T> insertById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingInsertById<T> extends OneAndAllEntity<T> {
/**
* Insert one entity.
*
* @return Inserted entity.
*/
@Override
T one(T object);
/**
* Insert a collection of entities.
*
* @return Inserted entities
*/
@Override
Collection<? extends T> all(Collection<? extends T> objects);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T>, WithCollection<T> {
TerminatingInsertById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use.
*/
interface InsertByIdWithOptions<T>
extends TerminatingInsertById<T>, WithInsertOptions<T> {
/**
* Fluent method to specify options to use for execution.
*
* @param options to use for execution
*/
@Override
TerminatingInsertById<T> withOptions(InsertOptions options);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface InsertByIdInCollection<T> extends InsertByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
InsertByIdWithOptions<T> inCollection(String collection);
}
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface InsertByIdInScope<T> extends InsertByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
InsertByIdInCollection<T> inScope(String scope);
}
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface InsertByIdWithDurability<T> extends InsertByIdInScope<T>, WithDurability<T> {
@Override
InsertByIdInCollection<T> withDurability(DurabilityLevel durabilityLevel);
@Override
InsertByIdInCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
@@ -58,6 +128,11 @@ public interface ExecutableInsertByIdOperation {
InsertByIdWithDurability<T> withExpiry(Duration expiry);
}
/**
* Provides methods for constructing KV insert operations in a fluent way.
*
* @param <T> the entity type to insert
*/
interface ExecutableInsertById<T> extends InsertByIdWithExpiry<T> {}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.data.couchbase.core.ReactiveInsertByIdOperationSuppor
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
@@ -36,7 +37,7 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
@Override
public <T> ExecutableInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ExecutableInsertByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
@@ -44,25 +45,29 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final InsertOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveInsertByIdSupport<T> reactiveSupport;
ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel,
final Duration expiry) {
ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, final InsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, collection, persistTo,
replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, scope, collection,
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -76,31 +81,45 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
public TerminatingInsertById<T> withOptions(final InsertOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public InsertByIdInCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public InsertByIdInCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,35 +18,100 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.couchbase.core.query.WithConsistency;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllId;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithRemoveOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* Remove Operations on KV service.
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableRemoveByIdOperation {
/**
* Removes a document.
*/
ExecutableRemoveById removeById();
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingRemoveById extends OneAndAllId<RemoveResult> {
/**
* Remove one document based on the given ID.
*
* @param id the document ID.
* @return result of the remove
*/
@Override
RemoveResult one(String id);
/**
* Remove the documents in the collection.
*
* @param ids the document IDs.
* @return result of the removes.
*/
@Override
List<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById, WithCollection<RemoveResult> {
TerminatingRemoveById inCollection(String collection);
/**
* Fluent method to specify options.
*/
interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions<RemoveResult> {
/**
* Fluent method to specify options to use for execution
*
* @param options options to use for execution
*/
@Override
TerminatingRemoveById withOptions(RemoveOptions options);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection, WithDurability<RemoveResult> {
/**
* Fluent method to specify the collection.
*/
interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
RemoveByIdWithOptions inCollection(String collection);
}
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*/
interface RemoveByIdInScope extends RemoveByIdInCollection, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
RemoveByIdInCollection inScope(String scope);
}
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
@Override
RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel);
@Override
RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
@@ -55,6 +120,9 @@ public interface ExecutableRemoveByIdOperation {
RemoveByIdWithDurability withCas(Long cas);
}
/**
* Provides methods for constructing remove operations in a fluent way.
*/
interface ExecutableRemoveById extends RemoveByIdWithCas {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation {
@@ -35,30 +36,35 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
@Override
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
null);
return new ExecutableRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
static class ExecutableRemoveByIdSupport implements ExecutableRemoveById {
private final CouchbaseTemplate template;
private final String scope;
private final String collection;
private final RemoveOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Long cas;
private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String collection, final PersistTo persistTo,
final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, Long cas) {
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Long cas) {
this.template = template;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), scope, collection, options,
persistTo, replicateTo, durabilityLevel, cas);
this.cas = cas;
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), collection, persistTo,
replicateTo, durabilityLevel, cas);
}
@Override
@@ -72,30 +78,46 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
}
@Override
public TerminatingRemoveById inCollection(final String collection) {
public RemoveByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithCas withCas(final Long cas) {
Assert.notNull(cas, "CAS must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, cas);
public TerminatingRemoveById withOptions(final RemoveOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,22 +19,47 @@ import java.util.List;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.WithConsistency;
import org.springframework.data.couchbase.core.support.WithQuery;
import org.springframework.data.couchbase.core.support.WithQueryOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* RemoveBy Query Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableRemoveByQueryOperation {
/**
* Remove via the query service.
*/
<T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingRemoveByQuery<T> {
/**
* Remove all matching documents.
*
* @return RemoveResult for each matching document
*/
List<RemoveResult> all();
}
/**
* Fluent methods to specify the query
*
* @param <T> the entity type.
*/
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T>, WithQuery<T> {
TerminatingRemoveByQuery<T> matching(Query query);
@@ -45,26 +70,67 @@ public interface ExecutableRemoveByQueryOperation {
}
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithQuery<T>, WithCollection<T> {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryWithOptions<T> extends RemoveByQueryWithQuery<T>, WithQueryOptions<RemoveResult> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
RemoveByQueryWithQuery<T> withOptions(QueryOptions options);
}
RemoveByQueryWithQuery<T> inCollection(String collection);
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithOptions<T>, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
RemoveByQueryWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryInScope<T> extends RemoveByQueryInCollection<T>, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
RemoveByQueryInCollection<T> inScope(String scope);
}
@Deprecated
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInCollection<T> {
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInScope<T> {
@Deprecated
RemoveByQueryInCollection<T> consistentWith(QueryScanConsistency scanConsistency);
RemoveByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface RemoveByQueryWithConsistency<T> extends RemoveByQueryConsistentWith<T>, WithConsistency<T> {
@Override
RemoveByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
}
/**
* Provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type.
*/
interface ExecutableRemoveByQuery<T> extends RemoveByQueryWithConsistency<T> {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,9 +19,10 @@ import java.util.List;
import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.util.Assert;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.springframework.util.Assert;
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
@@ -35,8 +36,8 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
@Override
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED,
null);
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null,
null, null);
}
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
@@ -46,17 +47,21 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
private final Query query;
private final ReactiveRemoveByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
private final String scope;
private final String collection;
private final QueryOptions options;
ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency, String collection) {
final QueryScanConsistency scanConsistency, String scope, String collection, QueryOptions options) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveRemoveByQuerySupport<>(template.reactive(), domainType, query, scanConsistency,
collection);
scope, collection, options);
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
}
@Override
@@ -66,26 +71,43 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
@Deprecated
public RemoveByQueryInCollection<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
public RemoveByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryWithQuery<T> withOptions(final QueryOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,46 +18,117 @@ package org.springframework.data.couchbase.core;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithReplaceOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* Replace Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableReplaceByIdOperation {
/**
* Replace using the KV service.
*
* @param domainType the entity type to replace.
*/
<T> ExecutableReplaceById<T> replaceById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingReplaceById<T> extends OneAndAllEntity<T> {
/**
* Replace one entity.
*
* @return Replaced entity.
*/
@Override
T one(T object);
/**
* Replace a collection of entities.
*
* @return Replaced entities
*/
@Override
Collection<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T>, WithCollection<T> {
TerminatingReplaceById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdWithOptions<T> extends TerminatingReplaceById<T>, WithReplaceOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
TerminatingReplaceById<T> withOptions(ReplaceOptions options);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdInCollection<T> extends ReplaceByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
ReplaceByIdWithOptions<T> inCollection(String collection);
}
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdInScope<T> extends ReplaceByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
ReplaceByIdInCollection<T> inScope(String scope);
}
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface ReplaceByIdWithDurability<T> extends ReplaceByIdInScope<T>, WithDurability<T> {
@Override
ReplaceByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
@Override
ReplaceByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReplaceByIdWithExpiry<T> extends ReplaceByIdWithDurability<T>, WithExpiry<T> {
@Override
ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
}
/**
* Provides methods for constructing KV replace operations in a fluent way.
*
* @param <T> the entity type to replace
*/
interface ExecutableReplaceById<T> extends ReplaceByIdWithExpiry<T> {}
}

View File

@@ -18,11 +18,12 @@ package org.springframework.data.couchbase.core;
import java.time.Duration;
import java.util.Collection;
import org.springframework.util.Assert;
import org.springframework.data.couchbase.core.ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceByIdOperation {
@@ -36,7 +37,7 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
@Override
public <T> ExecutableReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ExecutableReplaceByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
@@ -44,25 +45,29 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final ReplaceOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveReplaceByIdSupport<T> reactiveSupport;
ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel,
final Duration expiry) {
ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, ReplaceOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(), domainType, scope, collection,
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -76,32 +81,46 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
public ReplaceByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel, expiry);
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public ReplaceByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel, expiry);
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public ReplaceByIdInScope<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel, expiry);
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@Override
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel, expiry);
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@Override
public TerminatingReplaceById<T> withOptions(final ReplaceOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@Override
public ReplaceByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,46 +18,118 @@ package org.springframework.data.couchbase.core;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithUpsertOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
/**
* Insert Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ExecutableUpsertByIdOperation {
/**
* Upsert using the KV service.
*
* @param domainType the entity type to upsert.
*/
<T> ExecutableUpsertById<T> upsertById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingUpsertById<T> extends OneAndAllEntity<T> {
/**
* Upsert one entity.
*
* @return Upserted entity.
*/
@Override
T one(T object);
/**
* Insert a collection of entities.
*
* @return Inserted entities
*/
@Override
Collection<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T>, WithCollection<T> {
TerminatingUpsertById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use.
*/
interface UpsertByIdWithOptions<T> extends TerminatingUpsertById<T>, WithUpsertOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
TerminatingUpsertById<T> withOptions(UpsertOptions options);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface UpsertByIdInCollection<T> extends UpsertByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
UpsertByIdWithOptions<T> inCollection(String collection);
}
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface UpsertByIdInScope<T> extends UpsertByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
UpsertByIdInCollection<T> inScope(String scope);
}
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface UpsertByIdWithDurability<T> extends UpsertByIdInScope<T>, WithDurability<T> {
@Override
UpsertByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
@Override
UpsertByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface UpsertByIdWithExpiry<T> extends UpsertByIdWithDurability<T>, WithExpiry<T> {
@Override
UpsertByIdWithDurability<T> withExpiry(Duration expiry);
}
/**
* Provides methods for constructing KV operations in a fluent way.
*
* @param <T> the entity type to upsert
*/
interface ExecutableUpsertById<T> extends UpsertByIdWithExpiry<T> {}
}

View File

@@ -18,12 +18,13 @@ package org.springframework.data.couchbase.core;
import java.time.Duration;
import java.util.Collection;
import org.springframework.util.Assert;
import org.springframework.data.couchbase.core.ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByIdOperation {
@@ -36,7 +37,7 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
@Override
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ExecutableUpsertByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
@@ -44,25 +45,29 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final UpsertOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveUpsertByIdSupport<T> reactiveSupport;
ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel,
final Duration expiry) {
ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, final UpsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(), domainType, scope, collection,
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -76,31 +81,45 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
public TerminatingUpsertById<T> withOptions(final UpsertOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public UpsertByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public UpsertByIdInScope<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,11 @@ package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.support.PseudoArgs;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by {@link ReactiveCouchbaseTemplate}.
* Defines common operations on the Couchbase data source, most commonly implemented by
* {@link ReactiveCouchbaseTemplate}.
*/
public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations {
@@ -44,4 +46,9 @@ public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOper
*/
CouchbaseClientFactory getCouchbaseClientFactory();
/**
* @@return the pseudoArgs from the ThreadLocal field of the CouchbaseOperations
*/
PseudoArgs<?> getPseudoArgs();
}

View File

@@ -25,6 +25,7 @@ import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import com.couchbase.client.java.Collection;
@@ -42,13 +43,14 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final ReactiveCouchbaseTemplateSupport templateSupport;
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
}
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
final TranslationService translationService) {
final TranslationService translationService) {
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
@@ -155,4 +157,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
templateSupport.setApplicationContext(applicationContext);
}
/**
* {@inheritDoc}
*/
@Override
public PseudoArgs<?> getPseudoArgs() {
return threadLocalArgs == null ? null : threadLocalArgs.get();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,9 +20,18 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Map;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllExistsReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithExistsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
/**
* Exists Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveExistsByIdOperation {
/**
@@ -30,6 +39,9 @@ public interface ReactiveExistsByIdOperation {
*/
ReactiveExistsById existsById();
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingExistsById extends OneAndAllExistsReactive {
/**
@@ -38,6 +50,7 @@ public interface ReactiveExistsByIdOperation {
* @param id the ID to perform the operation on.
* @return true if the document exists, false otherwise.
*/
@Override
Mono<Boolean> one(String id);
/**
@@ -46,21 +59,53 @@ public interface ReactiveExistsByIdOperation {
* @param ids the ids to check.
* @return a map consisting of the document IDs as the keys and if they exist as the value.
*/
@Override
Mono<Map<String, Boolean>> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById, WithCollection {
/**
* Fluent method to specify options.
*/
interface ExistsByIdWithOptions extends TerminatingExistsById, WithExistsOptions {
/**
* Allows to specify a different collection than the default one configured.
* Fluent method to specify options to use for execution.
*
* @param collection the collection to use in this scope.
* @param options to use for execution
*/
TerminatingExistsById inCollection(String collection);
@Override
TerminatingExistsById withOptions(ExistsOptions options);
}
interface ReactiveExistsById extends ExistsByIdWithCollection {}
/**
* Fluent method to specify the collection.
*/
interface ExistsByIdInCollection extends ExistsByIdWithOptions, InCollection {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
ExistsByIdWithOptions inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*/
interface ExistsByIdInScope extends ExistsByIdInCollection, InScope {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
ExistsByIdInCollection inScope(String scope);
}
/**
* Provides methods for constructing KV exists operations in a fluent way.
*/
interface ReactiveExistsById extends ExistsByIdInScope {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.couchbase.core;
import static com.couchbase.client.java.kv.ExistsOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -25,13 +23,18 @@ import reactor.util.function.Tuples;
import java.util.Collection;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.ExistsResult;
public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveExistsByIdOperationSupport.class);
ReactiveExistsByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -39,23 +42,32 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdSupport(template, null);
return new ReactiveExistsByIdSupport(template, null, null, null);
}
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
private final ReactiveCouchbaseTemplate template;
private final String scope;
private final String collection;
private final ExistsOptions options;
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String collection) {
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
final ExistsOptions options) {
this.template = template;
this.scope = scope;
this.collection = collection;
this.options = options;
}
@Override
public Mono<Boolean> one(final String id) {
return Mono.just(id).flatMap(
docId -> template.getCollection(collection).reactive().exists(id, existsOptions()).map(ExistsResult::exists))
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ExistsOptions.existsOptions());
LOG.trace("statement: {} scope: {} collection: {}", "exitsById", pArgs.getScope(), pArgs.getCollection());
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().exists(id, pArgs.getOptions()).map(ExistsResult::exists))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -72,9 +84,21 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
}
@Override
public TerminatingExistsById inCollection(final String collection) {
public ExistsByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, collection);
return new ReactiveExistsByIdSupport(template, scope, collection, options);
}
@Override
public TerminatingExistsById withOptions(final ExistsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveExistsByIdSupport(template, scope, collection, options);
}
@Override
public ExistsByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, scope, collection, options);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,22 @@ import reactor.core.publisher.Mono;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllReactive;
import org.springframework.data.couchbase.core.support.WithAnalyticsConsistency;
import org.springframework.data.couchbase.core.support.WithAnalyticsOptions;
import org.springframework.data.couchbase.core.support.WithAnalyticsQuery;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
/**
* FindByAnalytics Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveFindByAnalyticsOperation {
/**
@@ -36,7 +46,7 @@ public interface ReactiveFindByAnalyticsOperation {
<T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
* Terminating operations invoking the actual execution.
*/
interface TerminatingFindByAnalytics<T> extends OneAndAllReactive {
@@ -90,8 +100,53 @@ public interface ReactiveFindByAnalyticsOperation {
}
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use.
*/
interface FindByAnalyticsWithOptions<T> extends FindByAnalyticsWithQuery<T>, WithAnalyticsOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
TerminatingFindByAnalytics<T> withOptions(AnalyticsOptions options);
}
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindByAnalyticsInCollection<T> extends FindByAnalyticsWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindByAnalyticsWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindByAnalyticsInScope<T> extends FindByAnalyticsInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindByAnalyticsInCollection<T> inScope(String scope);
}
@Deprecated
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsWithQuery<T> {
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsInScope<T> {
/**
* Allows to override the default scan consistency.
@@ -103,17 +158,34 @@ public interface ReactiveFindByAnalyticsOperation {
}
interface FindByAnalyticsWithConsistency<T> extends FindByAnalyticsConsistentWith<T>, WithAnalyticsConsistency<T> {
interface FindByAnalyticsWithConsistency<T> extends FindByAnalyticsInScope<T>, WithAnalyticsConsistency<T> {
/**
* Allows to override the default scan consistency.
*
* @param scanConsistency the custom scan consistency to use for this analytics query.
*/
@Override
FindByAnalyticsWithQuery<T> withConsistency(AnalyticsScanConsistency scanConsistency);
}
interface ReactiveFindByAnalytics<T> extends FindByAnalyticsWithConsistency<T> {}
/**
* Result type override (Optional).
*/
interface FindByAnalyticsWithProjection<T> extends FindByAnalyticsWithConsistency<T> {
/**
* Define the target type fields should be mapped to. <br />
* Skip this step if you are anyway only interested in the original domain type.
*
* @param returnType must not be {@literal null}.
* @return new instance of {@link FindByAnalyticsWithConsistency}.
* @throws IllegalArgumentException if returnType is {@literal null}.
*/
<R> FindByAnalyticsWithConsistency<R> as(Class<R> returnType);
}
interface ReactiveFindByAnalytics<T> extends FindByAnalyticsWithProjection<T>, FindByAnalyticsConsistentWith<T> {}
}

View File

@@ -15,12 +15,12 @@
*/
package org.springframework.data.couchbase.core;
import com.couchbase.client.java.query.ReactiveQueryResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.data.couchbase.core.support.TemplateUtils;
import org.springframework.util.Assert;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.java.analytics.AnalyticsOptions;
@@ -39,41 +39,60 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED,
return new ReactiveFindByAnalyticsSupport<>(template, domainType, domainType, ALL_QUERY, null, null, null, null,
template.support());
}
static class ReactiveFindByAnalyticsSupport<T> implements ReactiveFindByAnalytics<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final Class<?> domainType;
private final Class<T> returnType;
private final AnalyticsQuery query;
private final AnalyticsScanConsistency scanConsistency;
private final String scope;
private final String collection;
private final AnalyticsOptions options;
private final ReactiveTemplateSupport support;
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency, ReactiveTemplateSupport support) {
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType,
final Class<T> returnType, final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency,
String scope, String collection, AnalyticsOptions options, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = query;
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
this.support = support;
}
@Override
public TerminatingFindByAnalytics<T> matching(AnalyticsQuery query) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
@Deprecated
public FindByAnalyticsWithQuery<T> consistentWith(AnalyticsScanConsistency scanConsistency) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
public FindByAnalyticsWithQuery<T> withConsistency(AnalyticsScanConsistency scanConsistency) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
public <R> FindByAnalyticsWithConsistency<R> as(final Class<R> returnType) {
Assert.notNull(returnType, "returnType must not be null!");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
@@ -114,7 +133,7 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
row.removeKey(TemplateUtils.SELECT_CAS);
return template.support().decodeEntity(id, row.toString(), cas, domainType);
return support.decodeEntity(id, row.toString(), cas, returnType);
});
});
}
@@ -139,6 +158,27 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
return count().map(count -> count > 0);
}
@Override
public TerminatingFindByAnalytics<T> withOptions(final AnalyticsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
public FindByAnalyticsInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
private String assembleEntityQuery(final boolean count) {
final String bucket = "`" + template.getBucketName() + "`";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,20 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithGetOptions;
import org.springframework.data.couchbase.core.support.WithProjectionId;
import com.couchbase.client.java.kv.GetOptions;
/**
* Get Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveFindByIdOperation {
/**
@@ -33,6 +43,11 @@ public interface ReactiveFindByIdOperation {
*/
<T> ReactiveFindById<T> findById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*
* @param <T> the entity type to use for the results.
*/
interface TerminatingFindById<T> extends OneAndAllIdReactive<T> {
/**
@@ -53,27 +68,67 @@ public interface ReactiveFindByIdOperation {
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T>, WithCollection<T> {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdWithOptions<T> extends TerminatingFindById<T>, WithGetOptions<T> {
/**
* Allows to specify a different collection than the default one configured.
* Fluent method to specify options to use for execution
*
* @param collection the collection to use in this scope.
* @param options options to use for execution
*/
TerminatingFindById<T> inCollection(String collection);
@Override
TerminatingFindById<T> withOptions(GetOptions options);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T>, WithProjectionId<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdInCollection<T> extends FindByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindByIdWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindByIdInScope<T> extends FindByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindByIdInCollection<T> inScope(String scope);
}
interface FindByIdWithProjection<T> extends FindByIdInScope<T>, WithProjectionId<T> {
/**
* Load only certain fields for the document.
*
* @param fields the projected fields to load.
*/
FindByIdWithCollection<T> project(String... fields);
FindByIdInCollection<T> project(String... fields);
}
/**
* Provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ReactiveFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -24,6 +24,9 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.core.error.DocumentNotFoundException;
@@ -34,28 +37,34 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByIdOperationSupport.class);
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdSupport<>(template, domainType, null, null, template.support());
return new ReactiveFindByIdSupport<>(template, domainType, null, null, null, null, template.support());
}
static class ReactiveFindByIdSupport<T> implements ReactiveFindById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final GetOptions options;
private final List<String> fields;
private final ReactiveTemplateSupport support;
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection,
List<String> fields, ReactiveTemplateSupport support) {
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String scope, String collection,
GetOptions options, List<String> fields, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.fields = fields;
this.support = support;
}
@@ -63,11 +72,17 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public Mono<T> one(final String id) {
return Mono.just(id).flatMap(docId -> {
GetOptions options = getOptions().transcoder(RawJsonTranscoder.INSTANCE);
if (fields != null && !fields.isEmpty()) {
options.project(fields);
GetOptions gOptions = options != null ? options : getOptions();
if (gOptions.build().transcoder() == null) {
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
return template.getCollection(collection).reactive().get(docId, options);
if (fields != null && !fields.isEmpty()) {
gOptions.project(fields);
}
PseudoArgs<GetOptions> pArgs = new PseudoArgs(template, scope, collection, gOptions);
LOG.trace("statement: {} scope: {} collection: {}", "findById", pArgs.getScope(), pArgs.getCollection());
return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())
.reactive().get(docId, pArgs.getOptions());
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorResume(throwable -> {
if (throwable instanceof RuntimeException) {
@@ -91,15 +106,28 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, fields, support);
public TerminatingFindById<T> withOptions(final GetOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
public FindByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
}
@Override
public FindByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
}
@Override
public FindByIdInScope<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields), support);
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,17 +21,20 @@ import reactor.core.publisher.Mono;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithConsistency;
import org.springframework.data.couchbase.core.support.WithDistinct;
import org.springframework.data.couchbase.core.support.WithProjection;
import org.springframework.data.couchbase.core.support.WithQuery;
import org.springframework.data.couchbase.core.support.WithQueryOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* ReactiveFindByQueryOperation
* ReactiveFindByQueryOperation<br>
* Queries the N1QL service.
*
* @author Michael Nitschinger
* @author Michael Reiche
@@ -39,7 +42,7 @@ import com.couchbase.client.java.query.QueryScanConsistency;
public interface ReactiveFindByQueryOperation {
/**
* Queries the N1QL service.
* Create the operation for the domainType
*
* @param domainType the entity type to use for the results.
*/
@@ -86,18 +89,19 @@ public interface ReactiveFindByQueryOperation {
*/
Mono<Boolean> exists();
QueryOptions buildOptions(QueryOptions options);
}
/**
* Terminating operations invoking the actual query execution.
* Fluent methods to filter by query
*
* @author Christoph Strobl
* @since 2.0
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T>, WithQuery<T> {
/**
* Set the filter for the query to be used.
* Set the filter {@link Query} to be used.
*
* @param query must not be {@literal null}.
* @throws IllegalArgumentException if query is {@literal null}.
@@ -108,7 +112,7 @@ public interface ReactiveFindByQueryOperation {
* Set the filter {@link QueryCriteriaDefinition criteria} to be used.
*
* @param criteria must not be {@literal null}.
* @return new instance of {@link ExecutableFindByQueryOperation.ExecutableFindByQuery}.
* @return new instance of {@link TerminatingFindByQuery}.
* @throws IllegalArgumentException if criteria is {@literal null}.
*/
default TerminatingFindByQuery<T> matching(QueryCriteriaDefinition criteria) {
@@ -118,27 +122,42 @@ public interface ReactiveFindByQueryOperation {
}
/**
* Collection override (optional).
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryInCollection<T> extends FindByQueryWithQuery<T>, WithCollection<T> {
interface FindByQueryWithOptions<T> extends FindByQueryWithQuery<T>, WithQueryOptions<T> {
/**
* Explicitly set the name of the collection to perform the query on. <br />
* Skip this step to use the default collection derived from the domain type.
*
* @param collection must not be {@literal null} nor {@literal empty}.
* @return new instance of {@link FindByQueryWithProjection}.
* @throws IllegalArgumentException if collection is {@literal null}.
* @param options options to use for execution
*/
FindByQueryWithQuery<T> inCollection(String collection);
TerminatingFindByQuery<T> withOptions(QueryOptions options);
}
/**
* @deprecated
* @see FindByQueryWithConsistency
* Fluent method to specify the collection
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryInCollection<T> extends FindByQueryWithOptions<T>, InCollection<T> {
FindByQueryWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryInScope<T> extends FindByQueryInCollection<T>, InScope<T> {
FindByQueryInCollection<T> inScope(String scope);
}
/**
* To be removed at the next major release. use WithConsistency instead
*
* @param <T> the entity type to use for the results.
*/
@Deprecated
interface FindByQueryConsistentWith<T> extends FindByQueryInCollection<T> {
interface FindByQueryConsistentWith<T> extends FindByQueryInScope<T> {
/**
* Allows to override the default scan consistency.
@@ -146,10 +165,15 @@ public interface ReactiveFindByQueryOperation {
* @param scanConsistency the custom scan consistency to use for this query.
*/
@Deprecated
FindByQueryInCollection<T> consistentWith(QueryScanConsistency scanConsistency);
FindByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
}
/**
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithConsistency<T> extends FindByQueryConsistentWith<T>, WithConsistency<T> {
/**
@@ -162,7 +186,9 @@ public interface ReactiveFindByQueryOperation {
}
/**
* Result type override (optional).
* Fluent method to specify a return type different than the the entity type to use for the results.
*
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithProjection<T> extends FindByQueryWithConsistency<T> {
@@ -178,9 +204,9 @@ public interface ReactiveFindByQueryOperation {
}
/**
* Distinct Find support.
* Fluent method to specify DISTINCT fields
*
* @author Michael Reiche
* @param <T> the entity type to use for the results.
*/
interface FindByQueryWithDistinct<T> extends FindByQueryWithProjection<T>, WithDistinct<T> {
@@ -194,6 +220,11 @@ public interface ReactiveFindByQueryOperation {
FindByQueryWithProjection<T> distinct(String[] distinctFields);
}
/**
* provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ReactiveFindByQuery<T> extends FindByQueryWithDistinct<T> {}
}

View File

@@ -18,11 +18,15 @@ package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.couchbase.core.support.TemplateUtils;
import org.springframework.util.Assert;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.couchbase.client.java.query.ReactiveQueryResult;
@@ -38,14 +42,16 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByQueryOperationSupport.class);
public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY,
QueryScanConsistency.NOT_BOUNDED, null, null, template.support());
return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY, null, null, null, null, null,
template.support());
}
static class ReactiveFindByQuerySupport<T> implements ReactiveFindByQuery<T> {
@@ -56,23 +62,30 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private final Query query;
private final QueryScanConsistency scanConsistency;
private final String collection;
private String scope;
private final String[] distinctFields;
// this would hold scanConsistency etc. from the fluent api if they were converted from standalone fields
// withScope(scopeName) could put raw("query_context",default:<bucket>.<scope>)
// this is not the options argument in save( entity, options ). That becomes query.getCouchbaseOptions()
private final QueryOptions options;
private final ReactiveTemplateSupport support;
ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType,
final Class<T> returnType, final Query query, final QueryScanConsistency scanConsistency,
final String collection, final String[] distinctFields, final ReactiveTemplateSupport support) {
this.support = support;
final Class<T> returnType, final Query query, final QueryScanConsistency scanConsistency, final String scope,
final String collection, final QueryOptions options, final String[] distinctFields,
final ReactiveTemplateSupport support) {
Assert.notNull(domainType, "domainType must not be null!");
Assert.notNull(returnType, "returnType must not be null!");
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.query = query;
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
this.distinctFields = distinctFields;
this.support = support;
}
@Override
@@ -83,42 +96,56 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
} else {
scanCons = scanConsistency;
}
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, scope, collection,
options, distinctFields, support);
}
@Override
public FindByQueryInCollection<T> inCollection(String collection) {
public TerminatingFindByQuery<T> withOptions(final QueryOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public FindByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public FindByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
@Deprecated
public FindByQueryConsistentWith<T> consistentWith(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public FindByQueryWithConsistency<T> withConsistency(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public <R> FindByQueryWithConsistency<R> as(Class<R> returnType) {
Assert.notNull(returnType, "returnType must not be null!");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public FindByQueryWithDistinct<T> distinct(String[] distinctFields) {
Assert.notNull(distinctFields, "distinctFields must not be null!");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields, support);
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
@@ -134,12 +161,14 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public Flux<T> all() {
return Flux.defer(() -> {
String statement = assembleEntityQuery(false, distinctFields);
Mono<ReactiveQueryResult> allResult = this.collection == null
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
LOG.trace("statement: {} {}", "findByQuery", statement);
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
query.buildQueryOptions(scanConsistency))
: template.getCouchbaseClientFactory().getScope().reactive().query(statement,
query.buildQueryOptions(scanConsistency));
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -170,15 +199,23 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
});
}
@Override
public QueryOptions buildOptions(QueryOptions options) {
QueryOptions opts = query.buildQueryOptions(options, scanConsistency);
return opts;
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
String statement = assembleEntityQuery(true, distinctFields);
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
LOG.trace("statement: {} {}", "findByQuery", statement);
Mono<ReactiveQueryResult> countResult = this.collection == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
query.buildQueryOptions(scanConsistency))
: template.getCouchbaseClientFactory().getScope().reactive().query(statement,
query.buildQueryOptions(scanConsistency));
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return countResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -193,12 +230,11 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public Mono<Boolean> exists() {
return count().map(count -> count > 0);
} // not efficient, just need the first one
return count().map(count -> count > 0); // not efficient, just need the first one
}
private String assembleEntityQuery(final boolean count, String[] distinctFields) {
return query.toN1qlSelectString(template, this.collection, this.domainType, this.returnType, count,
distinctFields);
private String assembleEntityQuery(final boolean count, String[] distinctFields, String collection) {
return query.toN1qlSelectString(template, collection, this.domainType, this.returnType, count, distinctFields);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,25 +21,100 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.AnyIdReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.WithGetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
/**
* Find by id from replicas Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveFindFromReplicasByIdOperation {
/**
* Loads a document from a replica.
*
* @param domainType the entity type to use for the results.
*/
<T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
/**
* Terminating operations invoking the actual get execution.
*/
interface TerminatingFindFromReplicasById<T> extends AnyIdReactive<T> {
/**
* Finds one document based on the given ID.
*
* @param id the document ID.
* @return the entity if found.
*/
Mono<T> any(String id);
/**
* Finds a list of documents based on the given IDs.
*
* @param ids the document ID ids.
* @return the list of found entities.
*/
Flux<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T>, WithCollection<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdWithOptions<T> extends TerminatingFindFromReplicasById<T>, WithGetAnyReplicaOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options options to use for execution
*/
@Override
TerminatingFindFromReplicasById<T> withOptions(GetAnyReplicaOptions options);
}
interface ReactiveFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdInCollection<T> extends FindFromReplicasByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
FindFromReplicasByIdWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface FindFromReplicasByIdInScope<T> extends FindFromReplicasByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
FindFromReplicasByIdInCollection<T> inScope(String scope);
}
/**
* Provides methods for constructing get operations in a fluent way.
*
* @param <T> the entity type to use for the results
*/
interface ReactiveFindFromReplicasById<T> extends FindFromReplicasByIdInScope<T> {}
}

View File

@@ -22,6 +22,9 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.RawJsonTranscoder;
@@ -30,6 +33,7 @@ import com.couchbase.client.java.kv.GetAnyReplicaOptions;
public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFindFromReplicasByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindFromReplicasByIdOperationSupport.class);
ReactiveFindFromReplicasByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -37,7 +41,8 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null, template.support());
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null, null, null,
template.support());
}
static class ReactiveFindFromReplicasByIdSupport<T> implements ReactiveFindFromReplicasById<T> {
@@ -45,23 +50,33 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
private final ReactiveCouchbaseTemplate template;
private final Class<?> domainType;
private final Class<T> returnType;
private final String scope;
private final String collection;
private final GetAnyReplicaOptions options;
private final ReactiveTemplateSupport support;
ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class<?> domainType, Class<T> returnType,
String collection, ReactiveTemplateSupport support) {
String scope, String collection, GetAnyReplicaOptions options, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.support = support;
}
@Override
public Mono<T> any(final String id) {
return Mono.just(id).flatMap(docId -> {
GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE);
return template.getCollection(collection).reactive().getAnyReplica(docId, options);
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
if (garOptions.build().transcoder() == null) {
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions);
LOG.trace("statement: {} scope: {} collection: {}", "getAnyReplica", pArgs.getScope(), pArgs.getCollection());
return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())
.reactive().getAnyReplica(docId, pArgs.getOptions());
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
@@ -78,9 +93,24 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
public TerminatingFindFromReplicasById<T> withOptions(final GetAnyReplicaOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
support);
}
@Override
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection, support);
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
support);
}
@Override
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,43 +21,117 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithInsertOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* Insert Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveInsertByIdOperation {
/**
* Insert using the KV service.
*
* @param domainType the entity type to insert.
*/
<T> ReactiveInsertById<T> insertById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingInsertById<T> extends OneAndAllEntityReactive<T> {
/**
* Insert one entity.
*
* @return Inserted entity.
*/
@Override
Mono<T> one(T object);
/**
* Insert a collection of entities.
*
* @return Inserted entities
*/
@Override
Flux<? extends T> all(Collection<? extends T> objects);
InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T>, WithCollection<T> {
TerminatingInsertById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*/
interface InsertByIdWithOptions<T> extends TerminatingInsertById<T>, WithInsertOptions<T> {
/**
* Fluent method to specify options to use for execution.
*
* @param options to use for execution
*/
@Override
TerminatingInsertById<T> withOptions(InsertOptions options);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*/
interface InsertByIdInCollection<T> extends InsertByIdWithOptions<T>, InCollection<T> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
InsertByIdWithOptions<T> inCollection(String collection);
}
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*/
interface InsertByIdInScope<T> extends InsertByIdInCollection<T>, InScope<T> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
InsertByIdInCollection<T> inScope(String scope);
}
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface InsertByIdWithDurability<T> extends InsertByIdInScope<T>, WithDurability<T> {
@Override
InsertByIdInCollection<T> withDurability(DurabilityLevel durabilityLevel);
@Override
InsertByIdInCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface InsertByIdWithExpiry<T> extends InsertByIdWithDurability<T>, WithExpiry<T> {
@Override
InsertByIdWithDurability<T> withExpiry(Duration expiry);
}
/**
* Provides methods for constructing KV insert operations in a fluent way.
*
* @param <T> the entity type to insert
*/
interface ReactiveInsertById<T> extends InsertByIdWithExpiry<T> {}
}

View File

@@ -21,8 +21,10 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
@@ -32,6 +34,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
@@ -41,7 +44,7 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ReactiveInsertByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null, template.support());
}
@@ -49,19 +52,23 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final InsertOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, final InsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
@@ -71,20 +78,23 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public Mono<T> one(T object) {
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted ->
template.getCollection(collection).reactive()
.insert(converted.getId(), converted.export(), buildInsertOptions(converted))
.flatMap(result ->
support.applyUpdatedId(object, converted.getId())
.flatMap(
updatedObject -> support.applyUpdatedCas(updatedObject, result.cas())))
).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : InsertOptions.insertOptions());
LOG.trace("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(),
pArgs.getCollection(), pArgs.getOptions());
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(insertedObject -> (Mono<T>) support.applyUpdatedCas(insertedObject, result.cas()))))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
@@ -92,8 +102,9 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
return Flux.fromIterable(objects).flatMap(this::one);
}
private InsertOptions buildInsertOptions(CouchbaseDocument doc) { // CouchbaseDocument converted
final InsertOptions options = InsertOptions.insertOptions();
@Override
public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) { // CouchbaseDocument converted
options = options != null ? options : InsertOptions.insertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
@@ -108,32 +119,46 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
public TerminatingInsertById<T> withOptions(final InsertOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public InsertByIdInCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public InsertByIdInCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,45 +20,105 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithRemoveOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* Remove Operations on KV service.
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveRemoveByIdOperation {
/**
* Removes a document.
*/
ReactiveRemoveById removeById();
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingRemoveById extends OneAndAllIdReactive<RemoveResult> {
/**
* Remove one document based on the given ID.
*
* @param id the document ID.
* @return result of the remove
*/
@Override
Mono<RemoveResult> one(String id);
/**
* Remove the documents in the collection.
*
* @param ids the document IDs.
* @return result of the removes.
*/
@Override
Flux<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById, WithCollection<RemoveResult> {
TerminatingRemoveById inCollection(String collection);
/**
* Fluent method to specify options.
*/
interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions<RemoveResult> {
/**
* Fluent method to specify options to use for execution
*
* @param options options to use for execution
*/
TerminatingRemoveById withOptions(RemoveOptions options);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection, WithDurability<RemoveResult> {
/**
* Fluent method to specify the collection.
*/
interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
RemoveByIdWithOptions inCollection(String collection);
}
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*/
interface RemoveByIdInScope extends RemoveByIdInCollection, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
RemoveByIdInCollection inScope(String scope);
}
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
@Override
RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel);
@Override
RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface RemoveByIdWithCas extends RemoveByIdWithDurability {
RemoveByIdWithDurability withCas(Long cas);
}
/**
* Provides methods for constructing remove operations in a fluent way.
*/
interface ReactiveRemoveById extends RemoveByIdWithCas {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
@@ -37,22 +38,28 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE, null);
return new ReactiveRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
static class ReactiveRemoveByIdSupport implements ReactiveRemoveById {
private final ReactiveCouchbaseTemplate template;
private final String scope;
private final String collection;
private final RemoveOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Long cas;
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, Long cas) {
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Long cas) {
this.template = template;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
@@ -61,8 +68,13 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
@Override
public Mono<RemoveResult> one(final String id) {
return Mono.just(id).flatMap(docId -> template.getCollection(collection).reactive()
.remove(id, buildRemoveOptions()).map(r -> RemoveResult.from(docId, r))).onErrorMap(throwable -> {
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : RemoveOptions.removeOptions());
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions()))
.map(r -> RemoveResult.from(docId, r)))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
@@ -76,8 +88,8 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
return Flux.fromIterable(ids).flatMap(this::one);
}
private RemoveOptions buildRemoveOptions() {
final RemoveOptions options = RemoveOptions.removeOptions();
private RemoveOptions buildRemoveOptions(RemoveOptions options) {
options = options != null ? options : RemoveOptions.removeOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
@@ -90,28 +102,45 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
}
@Override
public RemoveByIdWithDurability inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null);
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(final Long cas) {
Assert.notNull(cas, "CAS must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, cas);
public RemoveByIdWithDurability inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public TerminatingRemoveById withOptions(final RemoveOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,20 +19,45 @@ import reactor.core.publisher.Flux;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.WithConsistency;
import org.springframework.data.couchbase.core.support.WithQuery;
import org.springframework.data.couchbase.core.support.WithQueryOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* RemoveBy Query Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveRemoveByQueryOperation {
/**
* Remove via the query service.
*/
<T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingRemoveByQuery<T> {
/**
* Remove all matching documents.
*
* @return RemoveResult for each matching document
*/
Flux<RemoveResult> all();
}
/**
* Fluent methods to specify the query
*
* @param <T> the entity type.
*/
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T>, WithQuery<RemoveResult> {
TerminatingRemoveByQuery<T> matching(Query query);
@@ -42,25 +67,67 @@ public interface ReactiveRemoveByQueryOperation {
}
}
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithQuery<T>, WithCollection<RemoveResult> {
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryWithOptions<T> extends RemoveByQueryWithQuery<T>, WithQueryOptions<RemoveResult> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
RemoveByQueryWithQuery<T> withOptions(QueryOptions options);
}
RemoveByQueryWithQuery<T> inCollection(String collection);
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithOptions<T>, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
RemoveByQueryWithOptions<T> inCollection(String collection);
}
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface RemoveByQueryInScope<T> extends RemoveByQueryInCollection<T>, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
RemoveByQueryInCollection<T> inScope(String scope);
}
@Deprecated
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInCollection<T> {
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInScope<T> {
@Deprecated
RemoveByQueryInCollection<T> consistentWith(QueryScanConsistency scanConsistency);
RemoveByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface RemoveByQueryWithConsistency<T> extends RemoveByQueryConsistentWith<T>, WithConsistency<RemoveResult> {
@Override
RemoveByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
}
/**
* Provides methods for constructing query operations in a fluent way.
*
* @param <T> the entity type.
*/
interface ReactiveRemoveByQuery<T> extends RemoveByQueryWithConsistency<T> {}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,14 +15,17 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.couchbase.core.support.TemplateUtils;
import org.springframework.util.Assert;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
@@ -34,13 +37,16 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByQueryOperationSupport.class);
public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, null);
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY,null, null,
null, null);
}
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
@@ -49,24 +55,32 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
private final Class<T> domainType;
private final Query query;
private final QueryScanConsistency scanConsistency;
private final String scope;
private final String collection;
private final QueryOptions options;
ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency, String collection) {
final QueryScanConsistency scanConsistency, String scope, String collection, QueryOptions options) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
this.options = options;
}
@Override
public Flux<RemoveResult> all() {
return Flux.defer(() -> {
String statement = assembleDeleteQuery();
Mono<ReactiveQueryResult> allResult = this.collection == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
: template.getCouchbaseClientFactory().getScope().reactive().query(statement, buildQueryOptions());
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options);
String statement = assembleDeleteQuery(pArgs.getCollection());
LOG.trace("statement: {}", statement);
Mono<ReactiveQueryResult> allResult = pArgs.getCollection() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()));
return allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -79,36 +93,53 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
});
}
private QueryOptions buildQueryOptions() {
return query.buildQueryOptions(scanConsistency);
private QueryOptions buildQueryOptions(QueryOptions options) {
return query.buildQueryOptions(options, scanConsistency);
}
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
@Deprecated
public RemoveByQueryInCollection<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
public RemoveByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection);
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
private String assembleDeleteQuery() {
private String assembleDeleteQuery(String collection) {
return query.toN1qlRemoveString(template, collection, this.domainType);
}
@Override
public RemoveByQueryWithQuery<T> withOptions(final QueryOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@Override
public RemoveByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,35 +21,102 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithReplaceOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* ReplaceOperations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveReplaceByIdOperation {
/**
* Replace using the KV service.
*
* @param domainType the entity type to replace.
*/
<T> ReactiveReplaceById<T> replaceById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingReplaceById<T> extends OneAndAllEntityReactive<T> {
/**
* Replace one entity.
*
* @return Replaced entity.
*/
Mono<T> one(T object);
/**
* Replace a collection of entities.
*
* @return Replaced entities
*/
Flux<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T>, WithCollection<T> {
TerminatingReplaceById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdWithOptions<T> extends TerminatingReplaceById<T>, WithReplaceOptions<RemoveResult> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
TerminatingReplaceById<T> withOptions(ReplaceOptions options);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdInCollection<T> extends ReplaceByIdWithOptions<T>, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
ReplaceByIdWithOptions<T> inCollection(String collection);
}
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface ReplaceByIdInScope<T> extends ReplaceByIdInCollection<T>, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
ReplaceByIdInCollection<T> inScope(String scope);
}
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface ReplaceByIdWithDurability<T> extends ReplaceByIdInScope<T>, WithDurability<T> {
ReplaceByIdInCollection<T> withDurability(DurabilityLevel durabilityLevel);
ReplaceByIdInCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
@@ -58,6 +125,11 @@ public interface ReactiveReplaceByIdOperation {
ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
}
/**
* Provides methods for constructing KV replace operations in a fluent way.
*
* @param <T> the entity type to replace
*/
interface ReactiveReplaceById<T> extends ReplaceByIdWithExpiry<T> {}
}

View File

@@ -21,7 +21,10 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
@@ -31,6 +34,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
@@ -40,7 +44,7 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ReactiveReplaceByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null, template.support());
}
@@ -48,19 +52,23 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final ReplaceOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, final ReplaceOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
@@ -70,17 +78,21 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public Mono<T> one(T object) {
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> {
return template.getCollection(collection).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(object, converted))
.flatMap(result -> support.applyUpdatedCas(object, result.cas()));
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ReplaceOptions.replaceOptions());
LOG.trace("statement: {} pArgs: {}", "replaceById", pArgs);
return Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> template.getCouchbaseClientFactory()
.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), object, converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(replacedObject -> (Mono<T>) support.applyUpdatedCas(replacedObject, result.cas()))))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
@@ -88,8 +100,8 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
return Flux.fromIterable(objects).flatMap(this::one);
}
private ReplaceOptions buildReplaceOptions(T object, CouchbaseDocument doc) {
final ReplaceOptions options = ReplaceOptions.replaceOptions();
private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, CouchbaseDocument doc) {
options = options != null ? options : ReplaceOptions.replaceOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
@@ -106,32 +118,46 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
public TerminatingReplaceById<T> withOptions(final ReplaceOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdWithDurability<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public ReplaceByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdInCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public ReplaceByIdInCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,43 +21,118 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
import org.springframework.data.couchbase.core.support.WithCollection;
import org.springframework.data.couchbase.core.support.WithUpsertOptions;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
/**
* Upsert Operations
*
* @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveUpsertByIdOperation {
/**
* Upsert using the KV service.
*
* @param domainType the entity type to upsert.
*/
<T> ReactiveUpsertById<T> upsertById(Class<T> domainType);
/**
* Terminating operations invoking the actual execution.
*/
interface TerminatingUpsertById<T> extends OneAndAllEntityReactive<T> {
/**
* Upsert one entity.
*
* @return Upserted entity.
*/
@Override
Mono<T> one(T object);
/**
* Insert a collection of entities.
*
* @return Inserted entities
*/
@Override
Flux<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T>, WithCollection<T> {
TerminatingUpsertById<T> inCollection(String collection);
/**
* Fluent method to specify options.
*
* @param <T> the entity type to use.
*/
interface UpsertByIdWithOptions<T> extends TerminatingUpsertById<T>, WithUpsertOptions<T> {
/**
* Fluent method to specify options to use for execution
*
* @param options to use for execution
*/
@Override
TerminatingUpsertById<T> withOptions(UpsertOptions options);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T>, WithDurability<T> {
/**
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.
*/
interface UpsertByIdInCollection<T> extends UpsertByIdWithOptions<T>, InCollection<Object> {
/**
* With a different collection
*
* @param collection the collection to use.
*/
@Override
UpsertByIdWithOptions<T> inCollection(String collection);
}
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
/**
* Fluent method to specify the scope.
*
* @param <T> the entity type to use for the results.
*/
interface UpsertByIdInScope<T> extends UpsertByIdInCollection<T>, InScope<Object> {
/**
* With a different scope
*
* @param scope the scope to use.
*/
@Override
UpsertByIdInCollection<T> inScope(String scope);
}
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
interface UpsertByIdWithDurability<T> extends UpsertByIdInScope<T>, WithDurability<T> {
@Override
UpsertByIdInCollection<T> withDurability(DurabilityLevel durabilityLevel);
@Override
UpsertByIdInCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface UpsertByIdWithExpiry<T> extends UpsertByIdWithDurability<T>, WithExpiry<T> {
@Override
UpsertByIdWithDurability<T> withExpiry(Duration expiry);
}
/**
* Provides methods for constructing KV operations in a fluent way.
*
* @param <T> the entity type to upsert
*/
interface ReactiveUpsertById<T> extends UpsertByIdWithExpiry<T> {}
}

View File

@@ -22,6 +22,7 @@ import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
@@ -40,7 +41,7 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
return new ReactiveUpsertByIdSupport<>(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null, template.support());
}
@@ -48,19 +49,23 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String scope;
private final String collection;
private final UpsertOptions options;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
final String collection, final UpsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
@@ -70,18 +75,21 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public Mono<T> one(T object) {
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted ->
template.getCollection(collection).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).flatMap(result ->
support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, result.cas())))
).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : UpsertOptions.upsertOptions());
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> (Mono<T>) support.applyUpdatedCas(updatedObject, result.cas()))))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
@@ -89,8 +97,8 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
return Flux.fromIterable(objects).flatMap(this::one);
}
private UpsertOptions buildUpsertOptions(CouchbaseDocument doc) {
final UpsertOptions options = UpsertOptions.upsertOptions();
private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocument doc) {
options = options != null ? options : UpsertOptions.upsertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
@@ -105,32 +113,46 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
public TerminatingUpsertById<T> withOptions(final UpsertOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveUpsertByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdWithDurability<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
public UpsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdInCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
public UpsertByIdInCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry, support);
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.data.couchbase.core.mapping;
import java.util.Locale;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
@@ -23,12 +25,11 @@ import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.Lazy;
import org.springframework.util.StringUtils;
import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Locale;
/**
* Implements annotated property representations of a given {@link Field} instance.
* <p/>
@@ -98,13 +99,20 @@ public class BasicCouchbasePersistentProperty extends AnnotationBasedPersistentP
// DATACOUCH-145: allows SDK's @Id annotation to be used
@Override
public boolean isIdProperty() {
if (super.isIdProperty()){
if (super.isIdProperty()) {
return true;
}
// is field named "id"
if(getField() != null && this.getFieldName().toLowerCase(Locale.ROOT).equals("id")){
if (getField() != null && this.getFieldName().toLowerCase(Locale.ROOT).equals("id")) {
return true;
}
return false;
}
public boolean isExpirationProperty() {
return isExpiration.get();
}
private final Lazy<Boolean> isExpiration = Lazy.of(() -> this.isAnnotationPresent(Expiration.class));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,4 +31,6 @@ public interface CouchbasePersistentProperty extends PersistentProperty<Couchbas
* The field name can be different from the actual property name by using a custom annotation.
*/
String getFieldName();
boolean isExpirationProperty();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to define a field to be substituted for META().expiration in a query
*
* @author Michael Reiche
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
public @interface Expiration {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
@@ -52,6 +54,7 @@ public class Query {
private QueryScanConsistency queryScanConsistency;
static private final Pattern WHERE_PATTERN = Pattern.compile("\\sWHERE\\s");
private static final Logger LOG = LoggerFactory.getLogger(Query.class);
public Query() {}
@@ -265,15 +268,15 @@ public class Query {
return sb.toString();
}
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) {
return toN1qlSelectString(template, null, domainClass, null, isCount, null);
}
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
boolean isCount) {
return toN1qlSelectString(template, collectionName, domainClass, null, isCount, null);
}
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, Class domainClass, boolean isCount) {
return toN1qlSelectString(template, null, domainClass, null, isCount, null);
}
public String toN1qlSelectString(ReactiveCouchbaseTemplate template, String collectionName, Class domainClass,
Class returnClass, boolean isCount, String[] distinctFields) {
StringBasedN1qlQueryParser.N1qlSpelValues n1ql = getN1qlSpelValues(template, collectionName, domainClass,
@@ -322,8 +325,7 @@ public class Query {
* @param scanConsistency
* @return QueryOptions
*/
public QueryOptions buildQueryOptions(QueryScanConsistency scanConsistency) {
QueryOptions options = QueryOptions.queryOptions();
public QueryOptions buildQueryOptions(QueryOptions options, QueryScanConsistency scanConsistency) {
if (options == null) { // add/override what we got from PseudoArgs
options = QueryOptions.queryOptions();
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
/**
* A common interface for all of Insert, Replace, Upsert that take Collection
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface InCollection<T> {
/**
* Specify collection
*
* @param collection - collection name
*/
Object inCollection(String collection);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,16 @@
package org.springframework.data.couchbase.core.support;
/**
* A common interface for all of Insert, Replace, Upsert that take Collection
* A common interface for all of Insert, Replace, Upsert that take options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithCollection<T> {
Object inCollection(String collectionName);
public interface InScope<T> {
/**
* Specify scope
*
* @param scope - scope name
*/
Object inScope(String scope);
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
public class PseudoArgs<OPTS> {
private final OPTS options;
private final String scopeName;
private final String collectionName;
public PseudoArgs(String scopeName, String collectionName, OPTS options) {
this.options = options;
this.scopeName = scopeName;
this.collectionName = collectionName;
}
/**
* return scope, collection and options in following precedence <br>
* 1) values from fluent api<br>
* 2) values from dynamic proxy (via template threadLocal)<br>
* 3) the values from the couchbaseClientFactory<br>
*
* @param template to hold
* @param scope
* @param collection
* @param options
*/
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options) {
// 1) values from the args (fluent api)
String scopeForQuery = scope;
String collectionForQuery = collection;
OPTS optionsForQuery = options;
// 2) from DynamicProxy via template threadLocal
scopeForQuery = scopeForQuery != null ? scopeForQuery : getThreadLocalScopeName(template);
collectionForQuery = collectionForQuery != null ? collectionForQuery : getThreadLocalCollectionName(template);
optionsForQuery = optionsForQuery != null ? optionsForQuery : getThreadLocalOptions(template);
// if a collection was specified but no scope, use the scope from the clientFactory
if (collectionForQuery != null && scopeForQuery == null) {
scopeForQuery = template.getCouchbaseClientFactory().getScope().name();
}
// specifying scope and collection = _default is not necessary and will fail if server doesn't have collections
if ((scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery))
&& (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery))) {
scopeForQuery = null;
collectionForQuery = null;
}
this.scopeName = scopeForQuery;
this.collectionName = collectionForQuery;
this.options = optionsForQuery;
}
/**
* @@return the options
*/
public OPTS getOptions() {
return this.options;
}
/**
* @@return the scope name
*/
public String getScope() {
return this.scopeName;
}
/**
* @@return the collection name
*/
public String getCollection() {
return this.collectionName;
}
/**
* @@return the options from the ThreadLocal field of the template
*/
private OPTS getThreadLocalOptions(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : (OPTS) (template.getPseudoArgs().getOptions());
}
/**
* @@return the scope name from the ThreadLocal field of the template
*/
private String getThreadLocalScopeName(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getScope();
}
/**
* @@return the collection name from the ThreadLocal field of the template
*/
private String getThreadLocalCollectionName(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getCollection();
}
}

View File

@@ -18,11 +18,16 @@ package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
/**
* A common interface for all of Insert, Replace, Upsert that take consistency
* Interface for operations that take AnalyticsScanConsistency
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithAnalyticsConsistency<T> {
/**
* Specify scan consistency
*
* @param scanConsistency - scan consistency
*/
Object withConsistency(AnalyticsScanConsistency scanConsistency);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.analytics.AnalyticsOptions;
/**
* Interface for operations that take AnalyticsOptions
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithAnalyticsOptions<T> {
/**
* Specify analytics options
*
* @param options - analytics options
*/
Object withOptions(AnalyticsOptions options);
}

View File

@@ -18,11 +18,16 @@ package org.springframework.data.couchbase.core.support;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
/**
* A common interface for all of Insert, Replace, Upsert that take Query
* Interface for operations that take AnalyticsQuery
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithAnalyticsQuery<T> {
/**
* Specify query
*
* @param query - query
*/
Object matching(AnalyticsQuery query);
}

View File

@@ -18,11 +18,16 @@ package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* A common interface for all of Insert, Replace, Upsert that take consistency
* A common interface operations that take scan consistency
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithConsistency<T> {
/**
* Specify scan consistency
*
* @param scanConsistency - scan consistency
*/
Object withConsistency(QueryScanConsistency scanConsistency);
}

View File

@@ -16,11 +16,16 @@
package org.springframework.data.couchbase.core.support;
/**
* A common interface for all of Insert, Replace, Upsert that take Distinct
* Interface for operations that take distinct fields
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithDistinct<T> {
/**
* Specify distinct field names
*
* @param distinctFields - distinct fields
*/
Object distinct(String[] distinctFields);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.ExistsOptions;
/**
* Interface for operations that take ExistsOptions
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithExistsOptions<T> {
/**
* Specify options
*
* @param options - exists options
*/
Object withOptions(ExistsOptions options);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
/**
* A interface for operations that take GetAnyReplicaOptions
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithGetAnyReplicaOptions<T> {
/**
* Specify options
*
* @param options - GetAnyReplicaOptions
*/
Object withOptions(GetAnyReplicaOptions options);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.GetOptions;
/**
* Interface for operations that take GetOptions
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithGetOptions<T> {
/**
* Specify options
*
* @param options - get options
*/
Object withOptions(GetOptions options);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.InsertOptions;
/**
* Interface for operations take insert options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithInsertOptions<T> {
/**
* Specify options
*
* @param options - insert options
*/
Object withOptions(InsertOptions options);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.query.QueryOptions;
/**
* A common interface for all of Insert, Replace, Upsert that take options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithQueryOptions<T> {
/**
* Specify options
*
* @param options - query options
*/
Object withOptions(QueryOptions options);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.RemoveOptions;
/**
* A common interface for all of Insert, Replace, Upsert that take options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithRemoveOptions<T> {
Object withOptions(RemoveOptions options);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.ReplaceOptions;
/**
* A common interface for all of Insert, Replace, Upsert that take options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithReplaceOptions<T> {
Object withOptions(ReplaceOptions options);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.java.kv.UpsertOptions;
/**
* A common interface for all of Insert, Replace, Upsert that take options
*
* @author Michael Reiche
* @param <T> - the entity class
*/
public interface WithUpsertOptions<T> {
Object withOptions(UpsertOptions options);
}

View File

@@ -15,10 +15,11 @@
*/
package org.springframework.data.couchbase.repository.query;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.TerminatingFindByQuery;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution.DeleteExecution;
import org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution.PagedExecution;
@@ -41,7 +42,7 @@ import org.springframework.util.Assert;
public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<CouchbaseOperations>
implements RepositoryQuery {
private final ExecutableFindByQueryOperation.ExecutableFindByQuery<?> findOperationWithProjection;
private final ExecutableFindByQuery<?> findOperationWithProjection;
/**
* Creates a new {@link AbstractCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and
@@ -86,7 +87,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
ExecutableFindByQuery<?> find = typeToRead == null ? findOperationWithProjection //
: findOperationWithProjection; // not yet implemented in core .as(typeToRead);
String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented
String collection = null;
CouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
@@ -101,7 +102,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
* @return
*/
private CouchbaseQueryExecution getExecution(ParameterAccessor accessor, Converter<Object, Object> resultProcessing,
ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation) {
ExecutableFindByQuery<?> operation) {
return new CouchbaseQueryExecution.ResultProcessingExecution(getExecutionToWrap(accessor, operation),
resultProcessing);
}
@@ -114,7 +115,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
* @return
*/
private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor,
ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation) {
ExecutableFindByQuery<?> operation) {
if (isDeleteQuery()) {
return new DeleteExecution(getOperations(), getQueryMethod());
@@ -130,7 +131,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
return new PagedExecution(operation, accessor.getPageable());
} else {
return (q, t, c) -> {
ExecutableFindByQueryOperation.TerminatingFindByQuery<?> find = operation.matching(q);
TerminatingFindByQuery<?> find = operation.matching(q);
if (isCountQuery()) {
return find.count();
}

View File

@@ -20,7 +20,7 @@ import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.repository.core.EntityMetadata;
@@ -47,7 +47,7 @@ public abstract class AbstractCouchbaseQueryBase<CouchbaseOperationsType> implem
private final CouchbaseQueryMethod method;
private final CouchbaseOperationsType operations;
private final EntityInstantiators instantiators;
private final ExecutableFindByQueryOperation.ExecutableFindByQuery<?> findOperationWithProjection;
private final ExecutableFindByQuery<?> findOperationWithProjection;
private final SpelExpressionParser expressionParser;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.couchbase.repository.query;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations;
import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation;
@@ -85,7 +86,7 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
? findOperationWithProjection //
: findOperationWithProjection; // note yet implemented in core .as(typeToRead);
String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented
String collection = null;
ReactiveCouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);

View File

@@ -19,7 +19,8 @@ import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.TerminatingFindByQuery;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@@ -92,10 +93,10 @@ interface CouchbaseQueryExecution {
*/
final class SlicedExecution implements CouchbaseQueryExecution {
private final ExecutableFindByQueryOperation.ExecutableFindByQuery<?> find;
private final ExecutableFindByQuery<?> find;
private final Pageable pageable;
public SlicedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery find, Pageable pageable) {
public SlicedExecution(ExecutableFindByQuery find, Pageable pageable) {
Assert.notNull(find, "Find must not be null!");
Assert.notNull(pageable, "Pageable must not be null!");
this.find = find;
@@ -123,10 +124,10 @@ interface CouchbaseQueryExecution {
*/
final class PagedExecution<FindWithQuery> implements CouchbaseQueryExecution {
private final ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation;
private final ExecutableFindByQuery<?> operation;
private final Pageable pageable;
public PagedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation, Pageable pageable) {
public PagedExecution(ExecutableFindByQuery<?> operation, Pageable pageable) {
Assert.notNull(operation, "Operation must not be null!");
Assert.notNull(pageable, "Pageable must not be null!");
this.operation = operation;
@@ -140,7 +141,7 @@ interface CouchbaseQueryExecution {
@Override
public Object execute(Query query, Class<?> type, String collection) {
int overallLimit = 0; // query.getLimit();
ExecutableFindByQueryOperation.TerminatingFindByQuery<?> matching = operation.matching(query);
TerminatingFindByQuery<?> matching = operation.matching(query);
// Apply raw pagination
query.with(pageable);
// Adjust limit if page would exceed the overall limit

View File

@@ -71,9 +71,23 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
return from(part, property, where(addMetaIfRequired(path, property)), iterator);
}
static Converter<? super CouchbasePersistentProperty, String> cvtr = (
source) -> new StringBuilder(source.getFieldName().length() + 2).append('`').append(source.getFieldName())
.append('`').toString();
static Converter<? super CouchbasePersistentProperty, String> cvtr = new MyConverter();
static class MyConverter implements Converter<CouchbasePersistentProperty, String> {
@Override
public String convert(CouchbasePersistentProperty source) {
if (source.isIdProperty()) {
return "META().id";
} else if (source.isVersionProperty()) {
return "META().cas";
} else if (source.isExpirationProperty()) {
return "META().expiration";
} else {
return new StringBuilder(source.getFieldName().length() + 2).append('`').append(source.getFieldName())
.append('`').toString();
}
}
}
@Override
protected QueryCriteria and(final Part part, final QueryCriteria base, final Iterator<Object> iterator) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.springframework.data.couchbase.repository.query;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.core.NamedQueries;
@@ -63,17 +63,16 @@ public class N1qlRepositoryQueryExecutor {
// counterpart to ReactiveN1qlRespositoryQueryExecutor,
Query query;
ExecutableFindByQueryOperation.ExecutableFindByQuery q;
ExecutableFindByQuery q;
if (queryMethod.hasN1qlAnnotation()) {
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(),
SPEL_PARSER, evaluationContextProvider, namedQueries).createQuery();
} else {
final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName())
.createQuery();
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter(), operations.getBucketName()).createQuery();
}
ExecutableFindByQueryOperation.ExecutableFindByQuery<?> operation = (ExecutableFindByQueryOperation.ExecutableFindByQuery<?>) operations
ExecutableFindByQuery<?> operation = (ExecutableFindByQuery<?>) operations
.findByQuery(domainClass).withConsistency(buildQueryScanConsistency());
if (queryMethod.isCountQuery()) {
return operation.matching(query).count();

View File

@@ -188,8 +188,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<Void> deleteAll() {
return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all()
.then();
return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all().then();
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ 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;
@@ -68,6 +69,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
couchbaseTemplate.removeByQuery(User.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated2.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated3.class).all();
}
@Test
@@ -93,12 +95,12 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
@Test
void withDurability()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = User.class; // for now, just User.class. There is no Durability annotation.
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
// insert, replace, upsert
for (OneAndAllEntity<User> operator : new OneAndAllEntity[] { couchbaseTemplate.insertById(clazz),
couchbaseTemplate.replaceById(clazz), couchbaseTemplate.upsertById(clazz) }) {
// create an entity of type clazz
Constructor cons = clazz.getConstructor(String.class, String.class, String.class);
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
@@ -112,7 +114,22 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
couchbaseTemplate.insertById(User.class).one(user);
}
// call to insert/replace/update
User returned = (User) operator.one(user);
User returned = null;
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
for (int i = 1; i != 5; i++) {
try {
returned = (User) operator.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
}
sleepSecs(1);
}
}
assertEquals(user, returned);
User found = couchbaseTemplate.findById(User.class).one(user.getId());
assertEquals(user, found);
@@ -210,8 +227,6 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = couchbaseTemplate.upsertById(User.class).one(user);
System.out.println(reactiveCouchbaseTemplate.support().getCas(user));
System.out.println(reactiveCouchbaseTemplate.support().getCas(modified));
assertEquals(user, modified);
// careful now - user and modified are the same object. The object has the new cas (@Version version)
@@ -236,8 +251,23 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
@Test
void insertByIdwithDurability() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = couchbaseTemplate.insertById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.NONE)
.one(user);
User inserted = null;
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
for (int i = 1; i != 5; i++) {
try {
inserted = couchbaseTemplate.insertById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.NONE)
.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
}
sleepSecs(1);
}
}
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(User.class).one(user));
}

View File

@@ -19,8 +19,10 @@ package org.springframework.data.couchbase.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
@@ -32,6 +34,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
@@ -46,30 +49,42 @@ 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.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import com.couchbase.client.java.kv.GetOptions;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Query tests Theses tests rely on a cb server running This class tests collection support with
* inCollection(collection) It should be identical to CouchbaseTemplateQueryIntegrationTests except for the setup and
* the inCollection(collectionName) calls. Testing without collections could also be done by this class simply by using
* scopeName = null and collectionName = null (except for inCollection() checks that the collectionName is not null)
* inCollection(collection), inScope(scope) and withOptions(options). Testing without collections could also be done by
* this class simply by using scopeName = null and collectionName = null
*
* @author Michael Reiche
*/
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
Airport vie = new Airport("airports::vie", "vie", "loww");
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
// collectionName = null;
// scopeName = null;
// no-op
}
@AfterAll
@@ -87,15 +102,26 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.findByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inScope(scopeName).inCollection(collectionName).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName)
.inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(Airport.class).inScope(otherScope).inCollection(otherCollection).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(otherScope)
.inCollection(otherCollection).all();
}
@AfterEach
@Override
public void afterEach() {
// first call the super method
// first do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
// query with REQUEST_PLUS to ensure that the remove has completed.
couchbaseTemplate.findByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.inCollection(collectionName).all();
// then call the super method
super.afterEach();
// then do processing for this class
// no-op
}
@Test
@@ -200,6 +226,9 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
.matching(specialUsers).all().collectList().block();
assertEquals(1, foundUsersReactive.size());
couchbaseTemplate.removeByQuery(UserSubmission.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
couchbaseTemplate.removeByQuery(UserSubmission.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
@Test
@@ -207,16 +236,20 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
User user1 = new User(UUID.randomUUID().toString(), "user1", "user1");
User user2 = new User(UUID.randomUUID().toString(), "user2", "user2");
couchbaseTemplate.upsertById(User.class).inCollection(collectionName).all(Arrays.asList(user1, user2));
couchbaseTemplate.upsertById(User.class).inScope(scopeName).inCollection(collectionName)
.all(Arrays.asList(user1, user2));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inCollection(collectionName).one(user2.getId()));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertTrue(couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName).one(user2.getId()));
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.inCollection(collectionName).all();
List<RemoveResult> result = couchbaseTemplate.removeByQuery(User.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).all();
assertEquals(2, result.size(), "should have deleted user1 and user2");
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).inCollection(collectionName).one(user2.getId()));
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user1.getId()));
assertNull(
couchbaseTemplate.findById(User.class).inScope(scopeName).inCollection(collectionName).one(user2.getId()));
}
@@ -315,7 +348,7 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
// count( distinct icao )
// not currently possible to have multiple fields in COUNT(DISTINCT field1, field2, ... ) due to MB43475
long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
Long count1 = reactiveCouchbaseTemplate.findByQuery(Airport.class).distinct(new String[] { "icao" })
.as(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).count()
.block();
assertEquals(2, count1);
@@ -336,4 +369,387 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
}
}
/**
* find . -name 'Exec*OperationSupport.java'|awk -F/ '{print $NF}'|sort| awk -F. '{print "* ", NR, ")",$1, ""}'<br>
* 1) ExecutableExistsByIdOperationSupport <br>
* 2) ExecutableFindByAnalyticsOperationSupport <br>
* 3) ExecutableFindByIdOperationSupport <br>
* 4) ExecutableFindByQueryOperationSupport <br>
* 5) ExecutableFindFromReplicasByIdOperationSupport <br>
* 6) ExecutableInsertByIdOperationSupport <br>
* 7) ExecutableRemoveByIdOperationSupport <br>
* 8) ExecutableRemoveByQueryOperationSupport <br>
* 9) ExecutableReplaceByIdOperationSupport <br>
* 10)ExecutableUpsertByIdOperationSupport <br>
*/
@Test
public void existsById() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
try {
Boolean exists = couchbaseTemplate.existsById().inScope(scopeName).inCollection(collectionName)
.withOptions(existsOptions).one(saved.getId());
assertTrue(exists, "Airport should exist: " + saved.getId());
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalytics() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
try {
List<Airport> found = couchbaseTemplate.findByAnalytics(Airport.class).inScope(scopeName)
.inCollection(collectionName).withOptions(options).all();
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findById() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findByQuery() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
try {
List<Airport> found = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.withOptions(options).all();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void findFromReplicasById() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(scopeName)
.inCollection(collectionName).withOptions(options).any(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void insertById() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withId(UUID.randomUUID().toString()));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void removeById() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
RemoveResult removeResult = couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(saved.getId());
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQuery() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.one(vie);
List<RemoveResult> removeResults = couchbaseTemplate.removeByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.withOptions(options).matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceById() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(insertOptions).one(vie);
Airport replaced = couchbaseTemplate.replaceById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie.withIcao("newIcao"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(replaced, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void upsertById() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.upsertById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(options).one(vie);
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(scopeName).inCollection(collectionName)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(scopeName).inCollection(collectionName).one(saved.getId());
}
}
@Test
public void existsByIdOther() { // 1
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
ExistsOptions existsOptions = ExistsOptions.existsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
Boolean exists = couchbaseTemplate.existsById().inScope(otherScope).inCollection(otherCollection)
.withOptions(existsOptions).one(saved.getId());
assertTrue(exists, "Airport should exist: " + saved.getId());
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOther() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
List<Airport> found = couchbaseTemplate.findByAnalytics(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).all();
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findByIdOther() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findByQueryOther() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
List<Airport> found = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).all();
assertEquals(saved.getId(), found.get(0).getId());
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void findFromReplicasByIdOther() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).any(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void insertByIdOther() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withId(UUID.randomUUID().toString()));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void removeByIdOther() { // 7
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
RemoveResult removeResult = couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(saved.getId());
assertEquals(saved.getId(), removeResult.getId());
}
@Test
public void removeByQueryOther() { // 8
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
List<RemoveResult> removeResults = couchbaseTemplate.removeByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all();
assertEquals(saved.getId(), removeResults.get(0).getId());
}
@Test
public void replaceByIdOther() { // 9
InsertOptions insertOptions = InsertOptions.insertOptions().timeout(Duration.ofSeconds(10));
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(insertOptions).one(vie);
Airport replaced = couchbaseTemplate.replaceById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie.withIcao("newIcao"));
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(replaced, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void upsertByIdOther() { // 10
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofSeconds(10));
GetOptions getOptions = GetOptions.getOptions().timeout(Duration.ofSeconds(10));
Airport saved = couchbaseTemplate.upsertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(options).one(vie);
try {
Airport found = couchbaseTemplate.findById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.withOptions(getOptions).one(saved.getId());
assertEquals(saved, found);
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void existsByIdOptions() { // 1 - Options
ExistsOptions options = ExistsOptions.existsOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> couchbaseTemplate.existsById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
@Disabled // needs analytics data set
public void findByAnalyticsOptions() { // 2
AnalyticsOptions options = AnalyticsOptions.analyticsOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.findByAnalytics(Airport.class)
.inScope(otherScope).inCollection(otherCollection).withOptions(options).all());
}
@Test
public void findByIdOptions() { // 3
GetOptions options = GetOptions.getOptions().timeout(Duration.ofNanos(10));
assertThrows(UnambiguousTimeoutException.class, () -> couchbaseTemplate.findById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
public void findByQueryOptions() { // 4
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class,
() -> couchbaseTemplate.findByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.inScope(otherScope).inCollection(otherCollection).withOptions(options).all());
}
@Test
public void findFromReplicasByIdOptions() { // 5
GetAnyReplicaOptions options = GetAnyReplicaOptions.getAnyReplicaOptions().timeout(Duration.ofNanos(1000));
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
try {
Airport found = couchbaseTemplate.findFromReplicasById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).any(saved.getId());
assertNull(found, "should not have found document in short timeout");
} finally {
couchbaseTemplate.removeById().inScope(otherScope).inCollection(otherCollection).one(saved.getId());
}
}
@Test
public void insertByIdOptions() { // 6
InsertOptions options = InsertOptions.insertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.insertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withId(UUID.randomUUID().toString())));
}
@Test
public void removeByIdOptions() { // 7 - options
Airport saved = couchbaseTemplate.insertById(Airport.class).inScope(otherScope).inCollection(otherCollection)
.one(vie);
RemoveOptions options = RemoveOptions.removeOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.removeById().inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.getId()));
}
@Test
public void removeByQueryOptions() { // 8 - options
QueryOptions options = QueryOptions.queryOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class,
() -> couchbaseTemplate.removeByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
.inScope(otherScope).inCollection(otherCollection).withOptions(options)
.matching(Query.query(QueryCriteria.where("iata").is(vie.getIata()))).all());
}
@Test
public void replaceByIdOptions() { // 9 - options
ReplaceOptions options = ReplaceOptions.replaceOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.replaceById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie.withIcao("newIcao")));
}
@Test
public void upsertByIdOptions() { // 10 - options
UpsertOptions options = UpsertOptions.upsertOptions().timeout(Duration.ofNanos(10));
assertThrows(AmbiguousTimeoutException.class, () -> couchbaseTemplate.upsertById(Airport.class).inScope(otherScope)
.inCollection(otherCollection).withOptions(options).one(vie));
}
}

View File

@@ -20,8 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE;
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
import java.time.Instant;
@@ -33,13 +31,10 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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.Config;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Submission;
@@ -69,11 +64,15 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
@BeforeEach
@Override
public void beforeEach() {
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE);
super.beforeEach();
// already setup by JavaIntegrationTests.beforeAll()
// ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
// reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE);
// ensure each test starts with clean state
couchbaseTemplate.removeByQuery(User.class).all();
couchbaseTemplate.findByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
@Test
@@ -108,13 +107,13 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
couchbaseTemplate.findById(User.class).one(user1.getId());
reactiveCouchbaseTemplate.findById(User.class).one(user1.getId()).block();
} finally {
couchbaseTemplate.removeByQuery(User.class).all();
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
User usery = couchbaseTemplate.findById(User.class).one("userx");
assertNull(usery, "usery should be null");
User userz = reactiveCouchbaseTemplate.findById(User.class).one("userx").block();
assertNull(userz, "uz should be null");
User usery = couchbaseTemplate.findById(User.class).one("user1");
assertNull(usery, "user1 should have been deleted");
User userz = reactiveCouchbaseTemplate.findById(User.class).one("user2").block();
assertNull(userz, "user2 should have been deleted");
}
@@ -136,6 +135,8 @@ class CouchbaseTemplateQueryIntegrationTests extends JavaIntegrationTests {
@Test
void findByMatchingQueryProjected() {
couchbaseTemplate.removeByQuery(UserSubmission.class).all();
UserSubmission user = new UserSubmission();
user.setId(UUID.randomUUID().toString());
user.setUsername("dave");

View File

@@ -224,7 +224,7 @@ class QueryCriteriaTests {
@Test
void testIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).in(args);
QueryCriteria c = where(i("name")).in((Object)args);
assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export());
JsonArray parameters = JsonArray.create();
assertEquals("`name` in ( $1 )", c.export(new int[1], parameters, null));
@@ -234,7 +234,7 @@ class QueryCriteriaTests {
@Test
void testNotIn() {
String[] args = new String[] { "gump", "davis" };
QueryCriteria c = where(i("name")).notIn(args);
QueryCriteria c = where(i("name")).notIn((Object)args);
assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export());
JsonArray parameters = JsonArray.create();
assertEquals("not( (`name` in ( $1 )) )", c.export(new int[1], parameters, null));

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ import com.couchbase.client.java.json.JacksonTransformers;
*/
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";
String username = "Administrator";
@@ -205,4 +205,15 @@ public class Config extends AbstractCouchbaseConfiguration {
return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter
}
static String scopeName = null;
@Override
protected String getScopeName() {
return scopeName;
}
public static void setScopeName(String scopeName) {
Config.scopeName = scopeName;
}
}

View File

@@ -1,42 +0,0 @@
/*
* 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.context.annotation.Configuration;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
/**
* Configuration that uses a scope. This is a separate class as it is difficult to debug if you forget to unset the
* scopeName and the config is used for non-collection operations.
*
* @Author Michael Reiche
*/
@Configuration
@EnableCouchbaseRepositories
public class ConfigScoped extends Config {
static String scopeName = null;
@Override
protected String getScopeName() {
return scopeName;
}
public static void setScopeName(String scopeName) {
ConfigScoped.scopeName = scopeName;
}
}

View File

@@ -1,7 +1,21 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
@@ -9,13 +23,16 @@ import org.springframework.data.mapping.context.MappingContext;
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
/**
* this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey
* while MappingCouchbaseConverter uses a DefaultCouchbaseTypeMapper
* typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : TYPEKEY_DEFAULT);
* this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey while MappingCouchbaseConverter
* uses a DefaultCouchbaseTypeMapper typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey :
* TYPEKEY_DEFAULT);
*
* @param mappingContext
* @param typeKey - the typeKey to be used (normally "_class")
*/
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext, final String typeKey) {
public CustomMappingCouchbaseConverter(
final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext,
final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.data.couchbase.domain;
import java.util.Optional;
import java.util.UUID;
import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
@@ -53,6 +52,7 @@ public class Person extends AbstractEntity {
this();
setFirstname(firstname);
setLastname(lastname);
setMiddlename("Nick");
}
public Person(int id, String firstname, String lastname) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,15 +15,16 @@
*/
package org.springframework.data.couchbase.domain;
import com.couchbase.client.java.query.QueryScanConsistency;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* @author Michael Reiche
@@ -107,6 +108,9 @@ public interface PersonRepository extends CrudRepository<Person, String> {
void deleteAll();
@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS)
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Person> findByAddressStreet(String street);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Person> findByMiddlename(String nickName);
}

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.
@@ -16,6 +16,7 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.core.RemoveResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -30,6 +31,7 @@ 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;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -55,6 +57,15 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIata(String iata);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
Flux<Airport> findAllPoliciesByApplicableTypes(String state, JsonArray applicableTypes);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} and icao != $1 ORDER BY effectiveDateTime DESC LIMIT 1")
Mono<Airport> findPolicySnapshotByPolicyIdAndEffectiveDateTime(String policyId, long effectiveDateTime);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} ORDER BY effectiveDateTime DESC")
Flux<Airport> findPolicySnapshotAll();
@Query("#{#n1ql.selectEntity} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> getAllByIata(String iata);

View File

@@ -109,7 +109,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
void shouldSaveAndFindAll() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = new Airport("airports::vie", "vie", "low4");
airportRepository.save(vie);
List<Airport> all = new ArrayList<>();
airportRepository.findAll().forEach(all::add);
@@ -133,6 +133,22 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
personRepository.save(person);
List<Person> persons = personRepository.findByAddressStreet("Maple");
assertEquals(1, persons.size());
List<Person> persons2 = personRepository.findByMiddlename("Nick");
assertEquals(1, persons2.size());
} finally {
personRepository.deleteById(person.getId().toString());
}
}
@Test
void annotatedFieldFind() {
Person person = null;
try {
person = new Person(1, "first", "last");
person.setMiddlename("Nick"); // middlename is stored as nickname
personRepository.save(person);
List<Person> persons2 = personRepository.findByMiddlename("Nick");
assertEquals(1, persons2.size());
} finally {
personRepository.deleteById(person.getId().toString());
}
@@ -144,7 +160,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Airport vie = null;
Airport xxx = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = new Airport("airports::vie", "vie", "low5");
airportRepository.save(vie);
xxx = new Airport("airports::xxx", "xxx", "xxxx");
airportRepository.save(xxx);
@@ -164,7 +180,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
void findBySimpleProperty() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = new Airport("airports::vie", "vie", "low6");
vie = airportRepository.save(vie);
List<Airport> airports = airportRepository.findAllByIata("vie");
assertEquals(1, airports.size());

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.
@@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -72,7 +76,7 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
Airport vie = null;
Airport jfk = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = new Airport("airports::vie", "vie", "low1");
airportRepository.save(vie).block();
jfk = new Airport("airports::jfk", "JFK", "xxxx");
airportRepository.save(jfk).block();
@@ -92,7 +96,7 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
void findBySimpleProperty() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = new Airport("airports::vie", "vie", "low2");
airportRepository.save(vie).block();
List<Airport> airports1 = airportRepository.findAllByIata("vie").collectList().block();
assertEquals(1, airports1.size());
@@ -121,6 +125,37 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
userRepository.delete(user).block();
}
@Test
void limitTest() {
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());
} finally {
airportRepository.delete(saved1).block();
airportRepository.delete(saved2).block();
}
}
public Mono<Airport> getPolicyByIdAndEffectiveDateTime(String policyId, Instant effectiveDateTime) {
return airportRepository
.findPolicySnapshotByPolicyIdAndEffectiveDateTime(policyId, effectiveDateTime.toEpochMilli())
// .map(Airport::getEntity)
.doOnError(
error -> System.out.println("MSG='Exception happened while retrieving Policy by Id and effectiveDateTime', "
+ "policyId={}, effectiveDateTime={}"));
}
@Test
void count() {
Set<String> iatas = new HashSet();

View File

@@ -32,6 +32,8 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.mapping.context.MappingContext;
@@ -75,6 +77,19 @@ class N1qlQueryCreatorTests {
assertEquals(query.export(), " WHERE " + where(i("firstname")).is("Oliver").export());
}
@Test
void createsQueryFieldAnnotationCorrectly() throws Exception {
String input = "findByMiddlename";
PartTree tree = new PartTree(input, Person.class);
Method method = PersonRepository.class.getMethod(input, String.class);
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), null, converter,
bucketName);
Query query = creator.createQuery();
assertEquals(query.export(), " WHERE " + where(i("nickname")).is("Oliver").export());
}
@Test
void queryParametersArray() throws Exception {
String input = "findByFirstnameIn";
@@ -89,9 +104,9 @@ class N1qlQueryCreatorTests {
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
assertEquals(expected.export(new int[1]), query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(actualOptions);
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}
@@ -111,9 +126,9 @@ class N1qlQueryCreatorTests {
Query expected = (new Query()).addCriteria(where(i("firstname")).in("Oliver", "Charles"));
assertEquals(expected.export(new int[1]), query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(actualOptions);
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}
@@ -133,9 +148,9 @@ class N1qlQueryCreatorTests {
assertEquals(expected.export(new int[1]), query.export(new int[1]));
JsonObject expectedOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
expected.buildQueryOptions(null, null).build().injectParams(expectedOptions);
JsonObject actualOptions = JsonObject.create();
expected.buildQueryOptions(null).build().injectParams(actualOptions);
expected.buildQueryOptions(null, null).build().injectParams(actualOptions);
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
}

View File

@@ -30,7 +30,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
@@ -101,7 +101,7 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
try {
Thread.sleep(3000);
} catch (Exception e) {}
ExecutableFindByQueryOperation.ExecutableFindByQuery q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) couchbaseTemplate
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate
.findByQuery(Airline.class).matching(query);
Optional<Airline> al = q.one();

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.couchbase.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
@@ -113,31 +114,57 @@ public abstract class ClusterAwareIntegrationTests {
public void afterEach() {}
/**
* This should probably be the first call in the @BeforeAll method of a test class.
* This will call super.beforeAll() when called as callSuperBeforeAll(new Object() {}); this trickery is necessary
* because super.beforeAll() cannot be used because it is a static method. it is possible and likely that the
* beforeAll() method of should still be called even when a test class defines its own beforeAll() method which would
* hide the beforeAll() of the super class.
* This trickery is not necessary for before/AfterEach, as those are not static methods
* This should probably be the first call in the @BeforeAll method of a test class. This will call super @BeforeAll
* methods when called as callSuperBeforeAll(new Object() {}); this trickery is necessary because super.beforeAll()
* cannot be used because it is a static method. it is possible and likely that the beforeAll() method of should still
* be called even when a test class defines its own beforeAll() method which would hide the beforeAll() of the super
* class. This trickery is not necessary for before/AfterEach, as those are not static methods
*
* @Author Michael Reiche
*
* @param createdHere - an object from a class defined in the calling class
*/
public static void callSuperBeforeAll(Object createdHere) {
callSuper(createdHere, "beforeAll");
callSuper(createdHere, BeforeAll.class);
}
// see comments for callSuperBeforeAll()
public static void callSuperAfterAll(Object createdHere) {
callSuper(createdHere, "afterAll");
callSuper(createdHere, AfterAll.class);
}
private static void callSuper(Object createdHere, String methodName) {
private static void callSuper(Object createdHere, Class annotationClass) {
try {
Method method = createdHere.getClass().getEnclosingClass().getSuperclass().getMethod(methodName);
method.invoke(null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
Class<?> encClass = createdHere.getClass().getEnclosingClass();
Class<?> theClass = encClass;
Annotation annotation = null;
Method invokedSuper = null;
if (annotationClass != BeforeAll.class && annotationClass != AfterAll.class) {
throw new RuntimeException("can only call super for BeforeAll and AfterAll " + annotationClass);
}
// look recursively for @BeforeAll or @AfterAll methods
// when one is found and executed, do not continue the recursive search
// as it is expected that the @BeforeAll or @AfterAll methods call
// any super methods explicitly - perhaps using callSuperBeforeAll() or callSuperAfterAll()
// Note that if the @BeforeAll and @AfterAll methods have different names, they will be
// called twice - once by this callSuper() mechanism and once by junit as the method will not be hidden
while ((theClass = theClass.getSuperclass()) != null) {
Method[] methods = theClass.getMethods();
for (Method m : methods) {
annotation = m.getAnnotation(annotationClass);
if (annotation != null) {
if (annotation != null) {
m.invoke(null);
invokedSuper = m;
}
}
}
if (invokedSuper != null) { // called method is responsible for calling any super methods
return;
}
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}

View File

@@ -19,6 +19,8 @@ import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMP
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -34,7 +36,8 @@ import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.manager.collection.CollectionManager;
import org.springframework.data.couchbase.domain.ConfigScoped;
import com.couchbase.client.java.manager.collection.CollectionSpec;
import com.couchbase.client.java.manager.collection.ScopeSpec;
/**
* Provides Collection support for integration tests
@@ -43,8 +46,10 @@ import org.springframework.data.couchbase.domain.ConfigScoped;
*/
public class CollectionAwareIntegrationTests extends JavaIntegrationTests {
public static String scopeName = "scope_" + randomString();
public static String collectionName = "collection_" + randomString();
public static String scopeName = "my_scope";// + randomString();
public static String otherScope = "other_scope";
public static String collectionName = "my_collection";// + randomString();
public static String otherCollection = "other_collection";// + randomString();
@BeforeAll
public static void beforeAll() {
@@ -57,20 +62,27 @@ public class CollectionAwareIntegrationTests extends JavaIntegrationTests {
waitForService(bucket, ServiceType.QUERY);
waitForQueryIndexerToHaveBucket(cluster, config().bucketname());
CollectionManager collectionManager = bucket.collections();
if (scopeName != null || collectionName != null) {
setupScopeCollection(cluster, scopeName, collectionName, collectionManager);
setupScopeCollection(cluster, scopeName, collectionName, collectionManager);
if (otherScope != null || otherCollection != null) {
// afterAll should be undoing the creation of scope etc
setupScopeCollection(cluster, otherScope, otherCollection, collectionManager);
}
ConfigScoped.setScopeName(scopeName);
ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigScoped.class);
Config.setScopeName(scopeName);
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// the Config class has been modified, these need to be loaded again
couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE);
}
@AfterAll
public static void afterAll(){
System.out.println("CollectionAwareIntegrationTests.afterAll()");
ConfigScoped.setScopeName(null);
callSuperBeforeAll(new Object() {});
public static void afterAll() {
Config.setScopeName(null);
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// the Config class has been modified, these need to be loaded again
couchbaseTemplate = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
reactiveCouchbaseTemplate = (ReactiveCouchbaseTemplate) ac.getBean(REACTIVE_COUCHBASE_TEMPLATE);
callSuperAfterAll(new Object() {});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors
* Copyright 2020-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,15 +39,16 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.domain.Config;
import com.couchbase.client.core.diagnostics.PingResult;
import com.couchbase.client.core.diagnostics.PingState;
@@ -80,7 +81,6 @@ import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.search.SearchQuery;
import com.couchbase.client.java.search.result.SearchResult;
import org.springframework.data.couchbase.domain.Config;
/**
* Extends the {@link ClusterAwareIntegrationTests} with java-client specific code.
@@ -91,8 +91,9 @@ import org.springframework.data.couchbase.domain.Config;
@Timeout(value = 10, unit = TimeUnit.MINUTES) // Safety timer so tests can't block CI executors
public class JavaIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired static public CouchbaseTemplate couchbaseTemplate;
@Autowired static public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
// Autowired annotation is not supported on static fields
static public CouchbaseTemplate couchbaseTemplate;
static public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
@BeforeAll
public static void beforeAll() {
@@ -141,15 +142,28 @@ public class JavaIntegrationTests extends ClusterAwareIntegrationTests {
ScopeSpec scopeSpec = ScopeSpec.create(scopeName);
CollectionSpec collSpec = CollectionSpec.create(collectionName, scopeName);
if (!scopeName.equals("_default")) {
collectionManager.createScope(scopeName);
if (!scopeName.equals(CollectionIdentifier.DEFAULT_SCOPE)) {
try {
collectionManager.createScope(scopeName);
waitUntilCondition(() -> scopeExists(collectionManager, scopeName));
ScopeSpec found = collectionManager.getScope(scopeName);
assertEquals(scopeSpec, found);
} catch (CouchbaseException e) {
if (!e.toString().contains("already exists")) {
e.printStackTrace();
throw e;
}
}
}
waitUntilCondition(() -> scopeExists(collectionManager, scopeName));
ScopeSpec found = collectionManager.getScope(scopeName);
assertEquals(scopeSpec, found);
collectionManager.createCollection(collSpec);
try {
collectionManager.createCollection(collSpec);
} catch (CouchbaseException e) {
if (!e.toString().contains("already exists")) {
e.printStackTrace();
throw e;
}
}
waitUntilCondition(() -> collectionExists(collectionManager, collSpec));
waitUntilCondition(
() -> collectionReady(cluster.bucket(config().bucketname()).scope(scopeName).collection(collectionName)));
@@ -258,6 +272,7 @@ public class JavaIntegrationTests extends ClusterAwareIntegrationTests {
String collectionName) {
CreatePrimaryQueryIndexOptions options = CreatePrimaryQueryIndexOptions.createPrimaryQueryIndexOptions();
options.timeout(Duration.ofSeconds(300));
options.ignoreIfExists(true);
final CreatePrimaryQueryIndexOptions.Built builtOpts = options.build();
final String indexName = builtOpts.indexName().orElse(null);
@@ -335,7 +350,6 @@ public class JavaIntegrationTests extends ClusterAwareIntegrationTests {
break;
} catch (CouchbaseException | IllegalStateException ex) {
// this is a pretty dirty hack to avoid a race where we don't know if the index is ready yet
System.out.println("createFtsCollectionIndex: " + i + " " + ex);
if (i < (maxTries - 1) && (ex.getMessage().contains("no planPIndexes for indexName")
|| ex.getMessage().contains("pindex_consistency mismatched partition")
|| ex.getMessage().contains("pindex not available"))) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.couchbase.util;
import static java.nio.charset.StandardCharsets.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import okhttp3.Credentials;
import okhttp3.FormBody;
@@ -43,6 +43,7 @@ public class UnmanagedTestCluster extends TestCluster {
private final String adminPassword;
private final int numReplicas;
private volatile String bucketname;
private long startTime = System.currentTimeMillis();
UnmanagedTestCluster(final Properties properties) {
seedHost = properties.getProperty("cluster.unmanaged.seed").split(":")[0];
@@ -69,8 +70,9 @@ public class UnmanagedTestCluster extends TestCluster {
.build())
.execute();
if (postResponse.code() != 202) {
throw new Exception("Could not create bucket: " + postResponse + ", Reason: " + postResponse.body().string());
String reason = postResponse.body().string();
if (postResponse.code() != 202 && !(reason.contains("Bucket with given name already exists"))) {
throw new Exception("Could not create bucket: " + postResponse + ", Reason: " + reason);
}
Response getResponse = httpClient
@@ -140,10 +142,13 @@ public class UnmanagedTestCluster extends TestCluster {
@Override
public void close() {
try {
httpClient
.newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword))
.url("http://" + seedHost + ":" + seedPort + "/pools/default/buckets/" + bucketname).delete().build())
.execute();
if (!bucketname.equals("my_bucket")) {
httpClient
.newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword))
.url("http://" + seedHost + ":" + seedPort + "/pools/default/buckets/" + bucketname).delete().build())
.execute();
}
System.out.println("elapsed: " + (System.currentTimeMillis() - startTime));
} catch (Exception ex) {
throw new RuntimeException(ex);
}

View File

@@ -21,9 +21,11 @@
- log details of the detection of placeholders in N1QL inline queries
- log additional debug info during automatic index creation
-->
<looger name="org.springframework.data.couchbase.core" level="debug"/>"
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
<logger name="org.springframework.data.couchbase.repository.query.SpatialViewQueryCreator" level="trace"/>
<logger name="org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery" level="trace"/>
<logger name="org.springframework.data.couchbase.repository.support.IndexManager" level="debug"/>
</configuration>
</configuration>