diff --git a/pom.xml b/pom.xml index a53a6a403..734eaad1e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,16 +25,16 @@ - - - org.testcontainers - testcontainers-bom - ${testcontainers} - pom - import - - - + + + org.testcontainers + testcontainers-bom + ${testcontainers} + pom + import + + + @@ -173,6 +173,7 @@ io.projectreactor reactor-test + 3.1.0.RELEASE test @@ -216,11 +217,11 @@ sonatype-snapshot https://oss.sonatype.org/content/repositories/snapshots - true - + true + - false - + false + jitpack.io @@ -284,6 +285,14 @@ org.asciidoctor asciidoctor-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 9fc59eb3a..6ba21ac05 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -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. @@ -28,6 +28,7 @@ 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 CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final Couch 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 @@ -180,4 +181,12 @@ private void prepareIndexCreator(final ApplicationContext context) { } } } + + /** + * {@inheritDoc} + */ + public void setThreadLocalArgs(PseudoArgs pseudoArgs) { + reactiveCouchbaseTemplate.setThreadLocalArgs(pseudoArgs); + } + } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java index 75185f521..357ba500b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java @@ -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 @@ 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 @@ interface TerminatingExistsById extends OneAndAllExists { * @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 @@ interface TerminatingExistsById extends OneAndAllExists { * @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 all(Collection ids); + } + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface ExistsByIdWithOptions extends TerminatingExistsById, WithExistsOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options options to use for execution + */ + @Override + TerminatingExistsById withOptions(ExistsOptions options); } - interface ExistsByIdWithCollection extends TerminatingExistsById, WithCollection { + /** + * + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + 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. + * + * @param the entity type to use for the results. + */ + interface ExistsByIdInScope extends ExistsByIdInCollection, InScope { /** - * Allows to specify a different collection than the default one configured. + * With a different scope * - * @param collection the collection to use in this scope. + * @param scope the scope to use. */ - TerminatingExistsById inCollection(String collection); + @Override + ExistsByIdInCollection inScope(String scope); } - interface ExecutableExistsById extends ExistsByIdWithCollection {} + /** + * Provides methods for constructing KV exists operations in a fluent way. + */ + interface ExecutableExistsById extends ExistsByIdInScope {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java index 3841fa647..5484d9ab5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java @@ -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 Map all(final Collection ids) { } @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); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java index 20962172a..2aed295fb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java @@ -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 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 @@ interface FindByAnalyticsWithQuery extends TerminatingFindByAnalytics, Wit } + /** + * Fluent method to specify options. + * + * @param the entity type to use. + */ + interface FindByAnalyticsWithOptions extends FindByAnalyticsWithQuery, WithAnalyticsOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + FindByAnalyticsWithQuery withOptions(AnalyticsOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindByAnalyticsInCollection extends FindByAnalyticsWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + FindByAnalyticsWithOptions inCollection(String collection); + } + + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindByAnalyticsInScope extends FindByAnalyticsInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindByAnalyticsInCollection inScope(String scope); + } + @Deprecated - interface FindByAnalyticsConsistentWith extends FindByAnalyticsWithQuery { + interface FindByAnalyticsConsistentWith extends FindByAnalyticsInScope { /** * Allows to override the default scan consistency. @@ -138,9 +192,24 @@ interface FindByAnalyticsWithConsistency extends FindByAnalyticsConsistentWit * @param scanConsistency the custom scan consistency to use for this analytics query. */ FindByAnalyticsConsistentWith withConsistency(AnalyticsScanConsistency scanConsistency); + } + + /** + * Result type override (Optional). + */ + interface FindByAnalyticsWithProjection extends FindByAnalyticsWithConsistency { + /** + * Define the target type fields should be mapped to.
+ * 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}. + */ + FindByAnalyticsWithConsistency as(Class returnType); } - interface ExecutableFindByAnalytics extends FindByAnalyticsWithConsistency {} + interface ExecutableFindByAnalytics extends FindByAnalyticsWithProjection {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java index d0e517136..8ba268c4a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java @@ -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,7 +21,9 @@ import org.springframework.data.couchbase.core.ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport; import org.springframework.data.couchbase.core.query.AnalyticsQuery; +import com.couchbase.client.java.analytics.AnalyticsOptions; import com.couchbase.client.java.analytics.AnalyticsScanConsistency; +import org.springframework.util.Assert; public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation { @@ -35,26 +37,34 @@ public ExecutableFindByAnalyticsOperationSupport(final CouchbaseTemplate templat @Override public ExecutableFindByAnalytics findByAnalytics(final Class 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 implements ExecutableFindByAnalytics { private final CouchbaseTemplate template; - private final Class domainType; + private final Class domainType; + private final Class returnType; private final ReactiveFindByAnalyticsSupport 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 domainType, - final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency) { + ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class domainType, + final Class 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); + this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, returnType, query, + scanConsistency, scope, collection, options); this.scanConsistency = scanConsistency; + this.scope = scope; + this.collection = collection; + this.options = options; } @Override @@ -74,18 +84,49 @@ public List all() { @Override public TerminatingFindByAnalytics 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 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 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 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 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 withConsistency(final AnalyticsScanConsistency scanConsistency) { - return new ExecutableFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); + } + + @Override + public FindByAnalyticsWithConsistency as(final Class returnType) { + Assert.notNull(returnType, "returnType must not be null!"); + return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java index ba6fee1fd..b2ce5dfda 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java @@ -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 @@ 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 { */ ExecutableFindById findById(Class domainType); + /** + * Terminating operations invoking the actual execution. + * + * @param the entity type to use for the results. + */ interface TerminatingFindById extends OneAndAllId { /** @@ -50,28 +65,66 @@ interface TerminatingFindById extends OneAndAllId { } - interface FindByIdWithCollection extends TerminatingFindById, WithCollection { - + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface FindByIdWithOptions extends TerminatingFindById, WithGetOptions { /** - * 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 inCollection(String collection); + @Override + TerminatingFindById withOptions(GetOptions options); + } + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindByIdInCollection extends FindByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + FindByIdWithOptions inCollection(String collection); } - interface FindByIdWithProjection extends FindByIdWithCollection, WithProjectionId { + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindByIdInScope extends FindByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindByIdInCollection inScope(String scope); + } + interface FindByIdWithProjection extends FindByIdInScope, WithProjectionId { /** * Load only certain fields for the document. * * @param fields the projected fields to load. */ - FindByIdWithCollection project(String... fields); - + @Override + FindByIdInScope project(String... fields); } + /** + * Provides methods for constructing query operations in a fluent way. + * + * @param the entity type to use for the results + */ interface ExecutableFindById extends FindByIdWithProjection {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java index caaa7c928..9fae1a790 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java @@ -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,6 +22,8 @@ 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 ExecutableFindById findById(Class domainType) { - return new ExecutableFindByIdSupport<>(template, domainType, null, null); + return new ExecutableFindByIdSupport<>(template, domainType, null, null, null, null); } static class ExecutableFindByIdSupport implements ExecutableFindById { private final CouchbaseTemplate template; private final Class domainType; + private final String scope; private final String collection; + private final GetOptions options; private final List fields; private final ReactiveFindByIdSupport reactiveSupport; - ExecutableFindByIdSupport(CouchbaseTemplate template, Class domainType, String collection, List fields) { + ExecutableFindByIdSupport(CouchbaseTemplate template, Class domainType, String scope, String collection, + GetOptions options, List 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); + this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, scope, collection, options, + fields); } @Override @@ -62,16 +70,29 @@ public Collection all(final Collection ids) { } @Override - public TerminatingFindById inCollection(final String collection) { + public TerminatingFindById withOptions(final GetOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields); + } + + @Override + public FindByIdWithOptions inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ExecutableFindByIdSupport<>(template, domainType, collection, fields); + return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields); } @Override - public FindByIdWithCollection project(String... fields) { + public FindByIdInCollection 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 project(String... fields) { Assert.notEmpty(fields, "Fields must not be null nor empty."); - return new ExecutableFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields)); + return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields)); } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java index 39e6c990e..8e6e603ab 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java @@ -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 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 { */ ExecutableFindByQuery findByQuery(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingFindByQuery extends OneAndAll { + /** * 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 one() { return Optional.ofNullable(oneValue()); } @@ -59,6 +72,7 @@ default Optional one() { * @throws IncorrectResultSizeDataAccessException if more than one match found. */ @Nullable + @Override T oneValue(); /** @@ -66,6 +80,7 @@ default Optional one() { * * @return {@link Optional#empty()} if no match found. */ + @Override default Optional first() { return Optional.ofNullable(firstValue()); } @@ -76,13 +91,15 @@ default Optional first() { * @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 all(); /** @@ -90,6 +107,7 @@ default Optional first() { * * @return a {@link Stream} of results. Never {@literal null}. */ + @Override Stream stream(); /** @@ -97,6 +115,7 @@ default Optional first() { * * @return total number of matching elements. */ + @Override long count(); /** @@ -104,15 +123,15 @@ default Optional first() { * * @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 the entity type to use for the results. */ interface FindByQueryWithQuery extends TerminatingFindByQuery, WithQuery { @@ -122,6 +141,7 @@ interface FindByQueryWithQuery extends TerminatingFindByQuery, WithQuery matching(Query query); /** @@ -131,25 +151,65 @@ interface FindByQueryWithQuery extends TerminatingFindByQuery, WithQuery matching(QueryCriteriaDefinition criteria) { return matching(Query.query(criteria)); } } - interface FindByQueryInCollection extends FindByQueryWithQuery, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface FindByQueryWithOptions extends FindByQueryWithQuery, WithQueryOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingFindByQuery withOptions(QueryOptions options); + } + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindByQueryInCollection extends FindByQueryWithOptions, InCollection { /** - * Allows to override the default scan consistency. + * With a different collection * - * @param collection the collection to use for this query. + * @param collection the collection to use. */ - FindByQueryWithQuery inCollection(String collection); + @Override + FindByQueryWithOptions inCollection(String collection); + } + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindByQueryInScope extends FindByQueryInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindByQueryInCollection inScope(String scope); } + /** + * To be removed at the next major release. use WithConsistency instead + * + * @param the entity type to use for the results. + */ @Deprecated - interface FindByQueryConsistentWith extends FindByQueryInCollection { + interface FindByQueryConsistentWith extends FindByQueryInScope { /** * Allows to override the default scan consistency. @@ -157,10 +217,14 @@ interface FindByQueryConsistentWith extends FindByQueryInCollection { * @param scanConsistency the custom scan consistency to use for this query. */ @Deprecated - FindByQueryInCollection consistentWith(QueryScanConsistency scanConsistency); - + FindByQueryInScope consistentWith(QueryScanConsistency scanConsistency); } + /** + * Fluent method to specify scan consistency. Scan consistency may also come from an annotation. + * + * @param the entity type to use for the results. + */ interface FindByQueryWithConsistency extends FindByQueryConsistentWith, WithConsistency { /** @@ -168,18 +232,20 @@ interface FindByQueryWithConsistency extends FindByQueryConsistentWith, Wi * * @param scanConsistency the custom scan consistency to use for this query. */ + @Override FindByQueryConsistentWith 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 the entity type to use for the results. */ interface FindByQueryWithProjection extends FindByQueryWithConsistency { /** * Define the target type fields should be mapped to.
- * 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 @@ interface FindByQueryWithProjection extends FindByQueryWithConsistency { } /** - * Distinct Find support. + * Fluent method to specify DISTINCT fields + * + * @param the entity type to use for the results. */ interface FindByQueryWithDistinct extends FindByQueryWithProjection, WithDistinct { @@ -200,14 +268,15 @@ interface FindByQueryWithDistinct extends FindByQueryWithProjection, WithD * @return new instance of {@link ExecutableFindByQuery}. * @throws IllegalArgumentException if field is {@literal null}. */ + @Override FindByQueryWithProjection 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 the entity type to use for the results */ - interface ExecutableFindByQuery extends FindByQueryWithDistinct {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java index ee4908984..3889543d7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -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,6 +22,7 @@ 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 ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableFindByQuery findByQuery(final Class domainType) { - return new ExecutableFindByQuerySupport(template, domainType, domainType, ALL_QUERY, - QueryScanConsistency.NOT_BOUNDED, null, null); + return new ExecutableFindByQuerySupport(template, domainType, domainType, ALL_QUERY, null, null, null, null, + null); } static class ExecutableFindByQuerySupport implements ExecutableFindByQuery { @@ -54,20 +55,24 @@ static class ExecutableFindByQuerySupport implements ExecutableFindByQuery private final Query query; private final ReactiveFindByQuerySupport 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 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(template.reactive(), domainType, returnType, query, - scanConsistency, collection, distinctFields); + scanConsistency, scope, collection, options, distinctFields); this.scanConsistency = scanConsistency; + this.scope = scope; this.collection = collection; + this.options = options; this.distinctFields = distinctFields; } @@ -94,42 +99,35 @@ public TerminatingFindByQuery matching(final Query query) { } 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 consistentWith(final QueryScanConsistency scanConsistency) { - return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + public FindByQueryInScope consistentWith(final QueryScanConsistency scanConsistency) { + return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override public FindByQueryConsistentWith 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 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 FindByQueryWithConsistency as(final Class resturnType) { - Assert.notNull(resturnType, "returnType must not be null!"); - return new ExecutableFindByQuerySupport<>(template, domainType, resturnType, query, scanConsistency, collection, - distinctFields); + public FindByQueryWithConsistency as(final Class returnType) { + Assert.notNull(returnType, "returnType must not be null!"); + return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override public FindByQueryWithProjection 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 long count() { public boolean exists() { return count() > 0; } + + @Override + public TerminatingFindByQuery 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 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 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); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java index bc744bceb..7a7b13dea 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java @@ -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 @@ 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. + */ ExecutableFindFromReplicasById findFromReplicasById(Class domainType); + /** + * Terminating operations invoking the actual get execution. + */ interface TerminatingFindFromReplicasById extends AnyId { - + /** + * 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 any(Collection ids); } - interface FindFromReplicasByIdWithCollection extends TerminatingFindFromReplicasById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdWithOptions extends TerminatingFindFromReplicasById, WithGetAnyReplicaOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options options to use for execution + */ + @Override + TerminatingFindFromReplicasById withOptions(GetAnyReplicaOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdInCollection extends FindFromReplicasByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + FindFromReplicasByIdWithOptions inCollection(String collection); + } - TerminatingFindFromReplicasById inCollection(String collection); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdInScope extends FindFromReplicasByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindFromReplicasByIdInCollection inScope(String scope); } - interface ExecutableFindFromReplicasById extends FindFromReplicasByIdWithCollection {} + /** + * Provides methods for constructing get operations in a fluent way. + * + * @param the entity type to use for the results + */ + interface ExecutableFindFromReplicasById extends FindFromReplicasByIdInScope {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java index eccebf35c..be2da671a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java @@ -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,6 +18,8 @@ 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 ExecutableFindFromReplicasById findFromReplicasById(Class domainType) { - return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, domainType, null); + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, domainType, null, null, null); } static class ExecutableFindFromReplicasByIdSupport implements ExecutableFindFromReplicasById { @@ -38,17 +40,21 @@ static class ExecutableFindFromReplicasByIdSupport implements ExecutableFindF private final CouchbaseTemplate template; private final Class domainType; private final Class returnType; + private final String scope; private final String collection; + private final GetAnyReplicaOptions options; private final ReactiveFindFromReplicasByIdSupport reactiveSupport; ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class domainType, Class 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); + scope, collection, options); } @Override @@ -62,9 +68,21 @@ public Collection any(Collection ids) { } @Override - public TerminatingFindFromReplicasById inCollection(final String collection) { + public TerminatingFindFromReplicasById withOptions(final GetAnyReplicaOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); + } + + @Override + public FindFromReplicasByIdWithOptions 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 inScope(final String scope) { + Assert.hasText(scope, "Scope must not be null nor empty."); + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java index 7451b631b..0c28fc4de 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java @@ -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 @@ 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. + */ ExecutableInsertById insertById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingInsertById extends OneAndAllEntity { + /** + * Insert one entity. + * + * @return Inserted entity. + */ @Override T one(T object); + /** + * Insert a collection of entities. + * + * @return Inserted entities + */ @Override Collection all(Collection objects); } - interface InsertByIdWithCollection extends TerminatingInsertById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use. + */ + interface InsertByIdWithOptions + extends TerminatingInsertById, WithInsertOptions { + /** + * Fluent method to specify options to use for execution. + * + * @param options to use for execution + */ + @Override + TerminatingInsertById withOptions(InsertOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface InsertByIdInCollection extends InsertByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + InsertByIdWithOptions inCollection(String collection); + } - TerminatingInsertById inCollection(String collection); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface InsertByIdInScope extends InsertByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + InsertByIdInCollection inScope(String scope); } - interface InsertByIdWithDurability extends InsertByIdWithCollection, WithDurability { + interface InsertByIdWithDurability extends InsertByIdInScope, WithDurability { - InsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + @Override + InsertByIdInCollection withDurability(DurabilityLevel durabilityLevel); - InsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + @Override + InsertByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); } @@ -58,6 +128,11 @@ interface InsertByIdWithExpiry extends InsertByIdWithDurability, WithExpir InsertByIdWithDurability withExpiry(Duration expiry); } + /** + * Provides methods for constructing KV insert operations in a fluent way. + * + * @param the entity type to insert + */ interface ExecutableInsertById extends InsertByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java index 7405fad64..7559199f5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java @@ -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,6 +22,7 @@ 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 ExecutableInsertByIdOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableInsertById insertById(final Class 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 @@ static class ExecutableInsertByIdSupport implements ExecutableInsertById { private final CouchbaseTemplate template; private final Class 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 reactiveSupport; - ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, - final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, - final Duration expiry) { + ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class 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); + this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, scope, collection, + options, persistTo, replicateTo, durabilityLevel, expiry); } @Override @@ -76,31 +81,45 @@ public Collection all(Collection objects) { } @Override - public TerminatingInsertById inCollection(final String collection) { + public TerminatingInsertById 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 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 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 withDurability(final DurabilityLevel durabilityLevel) { + public InsertByIdInCollection 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 withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public InsertByIdInCollection 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 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); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java index 6918bdf3f..67c5392da 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java @@ -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 @@ 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 { + /** + * 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 all(Collection ids); } - interface RemoveByIdWithCollection extends TerminatingRemoveById, WithCollection { + /** + * Fluent method to specify options. + */ + interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options options to use for execution + */ + @Override + TerminatingRemoveById withOptions(RemoveOptions options); + } + + /** + * Fluent method to specify the collection. + */ + interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + RemoveByIdWithOptions inCollection(String collection); + } - TerminatingRemoveById inCollection(String collection); + /** + * Fluent method to specify the scope. + */ + interface RemoveByIdInScope extends RemoveByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + RemoveByIdInCollection inScope(String scope); } - interface RemoveByIdWithDurability extends RemoveByIdWithCollection, WithDurability { + interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability { - RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + @Override + RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel); - RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + @Override + RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); } @@ -55,6 +120,9 @@ interface RemoveByIdWithCas extends RemoveByIdWithDurability { RemoveByIdWithDurability withCas(Long cas); } + /** + * Provides methods for constructing remove operations in a fluent way. + */ interface ExecutableRemoveById extends RemoveByIdWithCas {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java index bbdaf593c..a006f1d6e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java @@ -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 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 ExecutableRemoveByIdOperationSupport(final CouchbaseTemplate template) { @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 List all(final Collection ids) { } @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); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java index 788983f44..a6bfdf0cf 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java @@ -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 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. + */ ExecutableRemoveByQuery removeByQuery(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingRemoveByQuery { + /** + * Remove all matching documents. + * + * @return RemoveResult for each matching document + */ List all(); } + /** + * Fluent methods to specify the query + * + * @param the entity type. + */ interface RemoveByQueryWithQuery extends TerminatingRemoveByQuery, WithQuery { TerminatingRemoveByQuery matching(Query query); @@ -45,26 +70,67 @@ default TerminatingRemoveByQuery matching(QueryCriteriaDefinition criteria) { } - interface RemoveByQueryInCollection extends RemoveByQueryWithQuery, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryWithOptions extends RemoveByQueryWithQuery, WithQueryOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + RemoveByQueryWithQuery withOptions(QueryOptions options); + } - RemoveByQueryWithQuery inCollection(String collection); + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryInCollection extends RemoveByQueryWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + RemoveByQueryWithOptions inCollection(String collection); + } + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryInScope extends RemoveByQueryInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + RemoveByQueryInCollection inScope(String scope); } @Deprecated - interface RemoveByQueryConsistentWith extends RemoveByQueryInCollection { + interface RemoveByQueryConsistentWith extends RemoveByQueryInScope { @Deprecated - RemoveByQueryInCollection consistentWith(QueryScanConsistency scanConsistency); + RemoveByQueryInScope consistentWith(QueryScanConsistency scanConsistency); } interface RemoveByQueryWithConsistency extends RemoveByQueryConsistentWith, WithConsistency { - + @Override RemoveByQueryConsistentWith withConsistency(QueryScanConsistency scanConsistency); } + /** + * Provides methods for constructing query operations in a fluent way. + * + * @param the entity type. + */ interface ExecutableRemoveByQuery extends RemoveByQueryWithConsistency {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java index ff0fb7b5b..f85745985 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java @@ -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 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 ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) @Override public ExecutableRemoveByQuery removeByQuery(Class 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 implements ExecutableRemoveByQuery { @@ -46,17 +47,21 @@ static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuer private final Query query; private final ReactiveRemoveByQuerySupport reactiveSupport; private final QueryScanConsistency scanConsistency; + private final String scope; private final String collection; + private final QueryOptions options; ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class 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 List all() { @Override public TerminatingRemoveByQuery 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 consistentWith(final QueryScanConsistency scanConsistency) { - return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); + public RemoveByQueryInScope consistentWith(final QueryScanConsistency scanConsistency) { + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, + options); } @Override public RemoveByQueryConsistentWith 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 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 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 inScope(final String scope) { + Assert.hasText(scope, "Scope must not be null nor empty."); + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, + options); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java index c7aa94e1e..090efad1d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java @@ -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 @@ 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; +/** + * Insert Operations + * + * @author Christoph Strobl + * @since 2.0 + */ public interface ExecutableReplaceByIdOperation { + /** + * Replace using the KV service. + * + * @param domainType the entity type to replace. + */ ExecutableReplaceById replaceById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingReplaceById extends OneAndAllEntity { + /** + * Replace one entity. + * + * @return Replaced entity. + */ @Override T one(T object); + /** + * Replace a collection of entities. + * + * @return Replaced entities + */ @Override Collection all(Collection objects); } - interface ReplaceByIdWithCollection extends TerminatingReplaceById, WithCollection { - - TerminatingReplaceById inCollection(String collection); + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdWithOptions extends TerminatingReplaceById, WithReplaceOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingReplaceById withOptions(ReplaceOptions options); } - interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection, WithDurability { + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdInCollection extends ReplaceByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + ReplaceByIdWithOptions inCollection(String collection); + } - ReplaceByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdInScope extends ReplaceByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + ReplaceByIdInCollection inScope(String scope); + } - ReplaceByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + interface ReplaceByIdWithDurability extends ReplaceByIdInScope, WithDurability { + @Override + ReplaceByIdInScope withDurability(DurabilityLevel durabilityLevel); + @Override + ReplaceByIdInScope withDurability(PersistTo persistTo, ReplicateTo replicateTo); } interface ReplaceByIdWithExpiry extends ReplaceByIdWithDurability, WithExpiry { - @Override ReplaceByIdWithDurability withExpiry(final Duration expiry); } + /** + * Provides methods for constructing KV replace operations in a fluent way. + * + * @param the entity type to replace + */ interface ExecutableReplaceById extends ReplaceByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java index 399fa6969..677d4ca32 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -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,11 +18,12 @@ 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 ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableReplaceById replaceById(final Class 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 @@ static class ExecutableReplaceByIdSupport implements ExecutableReplaceById private final CouchbaseTemplate template; private final Class 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 reactiveSupport; - ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, - final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, - final Duration expiry) { + ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class 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); + this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(), domainType, scope, collection, + options, persistTo, replicateTo, durabilityLevel, expiry); } @Override @@ -76,32 +81,46 @@ public Collection all(Collection objects) { } @Override - public TerminatingReplaceById inCollection(final String collection) { + public ReplaceByIdWithOptions 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 withDurability(final DurabilityLevel durabilityLevel) { + public ReplaceByIdInScope 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 withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public ReplaceByIdInScope 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 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 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 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); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java index eb0d9522e..5e185c842 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java @@ -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 @@ 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. + */ ExecutableUpsertById upsertById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingUpsertById extends OneAndAllEntity { + /** + * Upsert one entity. + * + * @return Upserted entity. + */ @Override T one(T object); + /** + * Insert a collection of entities. + * + * @return Inserted entities + */ @Override Collection all(Collection objects); } - interface UpsertByIdWithCollection extends TerminatingUpsertById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use. + */ + interface UpsertByIdWithOptions extends TerminatingUpsertById, WithUpsertOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingUpsertById withOptions(UpsertOptions options); + } - TerminatingUpsertById inCollection(String collection); + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface UpsertByIdInCollection extends UpsertByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + UpsertByIdWithOptions inCollection(String collection); } - interface UpsertByIdWithDurability extends UpsertByIdWithCollection, WithDurability { + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface UpsertByIdInScope extends UpsertByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + UpsertByIdInCollection inScope(String scope); + } - UpsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + interface UpsertByIdWithDurability extends UpsertByIdInScope, WithDurability { + @Override + UpsertByIdInScope withDurability(DurabilityLevel durabilityLevel); - UpsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + @Override + UpsertByIdInScope withDurability(PersistTo persistTo, ReplicateTo replicateTo); } interface UpsertByIdWithExpiry extends UpsertByIdWithDurability, WithExpiry { - @Override UpsertByIdWithDurability withExpiry(Duration expiry); } + /** + * Provides methods for constructing KV operations in a fluent way. + * + * @param the entity type to upsert + */ interface ExecutableUpsertById extends UpsertByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java index 9ec260ce8..c9ab8a830 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java @@ -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,12 +18,13 @@ 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 ExecutableUpsertByIdOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableUpsertById upsertById(final Class 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 @@ static class ExecutableUpsertByIdSupport implements ExecutableUpsertById { private final CouchbaseTemplate template; private final Class 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 reactiveSupport; - ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, - final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, - final Duration expiry) { + ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class 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); + this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(), domainType, scope, collection, + options, persistTo, replicateTo, durabilityLevel, expiry); } @Override @@ -76,31 +81,45 @@ public Collection all(Collection objects) { } @Override - public TerminatingUpsertById inCollection(final String collection) { + public TerminatingUpsertById 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 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 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 withDurability(final DurabilityLevel durabilityLevel) { + public UpsertByIdInScope 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 withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public UpsertByIdInScope 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 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); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java index b438d10e3..a224bba4e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java @@ -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 @@ 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,14 @@ public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOper */ CouchbaseClientFactory getCouchbaseClientFactory(); + /** + * @param pseudoArgs - the metaArgs to set on the ThreadLocal field of the CouchbaseOperations + */ + void setThreadLocalArgs(PseudoArgs pseudoArgs); + + /** + * @@return the metaArgs from the ThreadLocal field of the CouchbaseOperations + */ + PseudoArgs getThreadLocalArgs(); + } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java index 4b01c9b9d..4ffde45eb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java @@ -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. @@ -25,6 +25,7 @@ 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; @@ -41,13 +42,14 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A private final CouchbaseConverter converter; private final PersistenceExceptionTranslator exceptionTranslator; private final CouchbaseTemplateSupport templateSupport; + private ThreadLocal> thrdLocalArgs = 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(); @@ -154,4 +156,20 @@ public void setApplicationContext(final ApplicationContext applicationContext) t templateSupport.setApplicationContext(applicationContext); } + /** + * {@inheritDoc} + */ + @Override + public PseudoArgs getThreadLocalArgs() { + return thrdLocalArgs == null ? null : thrdLocalArgs.get(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setThreadLocalArgs(PseudoArgs pseudoArgs) { + this.thrdLocalArgs.set(pseudoArgs); + } + } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java index 23251d923..49abbc4a4 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java @@ -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 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; +/** + * Insert 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 @@ interface TerminatingExistsById extends OneAndAllExistsReactive { * @param id the ID to perform the operation on. * @return true if the document exists, false otherwise. */ + @Override Mono one(String id); /** @@ -46,21 +59,53 @@ interface TerminatingExistsById extends OneAndAllExistsReactive { * @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> all(Collection ids); } - interface ExistsByIdWithCollection extends TerminatingExistsById, WithCollection { + /** + * Fluent method to specify options. + */ + interface ExistsByIdWithOptions extends TerminatingExistsById, WithExistsOptions { + /** + * Fluent method to specify options to use for execution. + * + * @param options to use for execution + */ + @Override + TerminatingExistsById withOptions(ExistsOptions options); + } + /** + * Fluent method to specify the collection. + */ + interface ExistsByIdInCollection extends ExistsByIdWithOptions, InCollection { /** - * Allows to specify a different collection than the default one configured. + * With a different collection * - * @param collection the collection to use in this scope. + * @param collection the collection to use. */ - TerminatingExistsById inCollection(String collection); + @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); } - interface ReactiveExistsById extends ExistsByIdWithCollection {} + /** + * Provides methods for constructing KV exists operations in a fluent way. + */ + interface ReactiveExistsById extends ExistsByIdInScope {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java index 65523214f..67b0024f7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java @@ -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,7 @@ */ package org.springframework.data.couchbase.core; -import static com.couchbase.client.java.kv.ExistsOptions.*; - +import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; @@ -25,8 +24,11 @@ import java.util.Collection; import java.util.Map; -import org.springframework.util.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.core.support.PseudoArgs; +import com.couchbase.client.java.kv.ExistsOptions; import com.couchbase.client.java.kv.ExistsResult; public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation { @@ -39,23 +41,33 @@ 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 static final Logger LOG = LoggerFactory.getLogger(ReactiveExistsByIdOperationSupport.class); 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 one(final String id) { - return Mono.just(id).flatMap( - docId -> template.getCollection(collection).reactive().exists(id, existsOptions()).map(ExistsResult::exists)) + PseudoArgs pArgs = new PseudoArgs<>(template, scope, collection, + options != null ? options : ExistsOptions.existsOptions()); + LOG.debug("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 Mono> all(final Collection ids) { } @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); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java index 8fd47e663..1d661b302 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java @@ -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 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 { ReactiveFindByAnalytics findByAnalytics(Class domainType); /** - * Compose find execution by calling one of the terminating methods. + * Terminating operations invoking the actual execution. */ interface TerminatingFindByAnalytics extends OneAndAllReactive { @@ -90,8 +100,53 @@ interface FindByAnalyticsWithQuery extends TerminatingFindByAnalytics, Wit } + /** + * Fluent method to specify options. + * + * @param the entity type to use. + */ + interface FindByAnalyticsWithOptions extends FindByAnalyticsWithQuery, WithAnalyticsOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingFindByAnalytics withOptions(AnalyticsOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindByAnalyticsInCollection extends FindByAnalyticsWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + FindByAnalyticsWithOptions inCollection(String collection); + } + + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindByAnalyticsInScope extends FindByAnalyticsInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindByAnalyticsInCollection inScope(String scope); + } + @Deprecated - interface FindByAnalyticsConsistentWith extends FindByAnalyticsWithQuery { + interface FindByAnalyticsConsistentWith extends FindByAnalyticsInScope { /** * Allows to override the default scan consistency. @@ -103,17 +158,34 @@ interface FindByAnalyticsConsistentWith extends FindByAnalyticsWithQuery { } - interface FindByAnalyticsWithConsistency extends FindByAnalyticsConsistentWith, WithAnalyticsConsistency { + interface FindByAnalyticsWithConsistency extends FindByAnalyticsInScope, WithAnalyticsConsistency { /** * Allows to override the default scan consistency. * * @param scanConsistency the custom scan consistency to use for this analytics query. */ + @Override FindByAnalyticsWithQuery withConsistency(AnalyticsScanConsistency scanConsistency); } - interface ReactiveFindByAnalytics extends FindByAnalyticsWithConsistency {} + /** + * Result type override (Optional). + */ + interface FindByAnalyticsWithProjection extends FindByAnalyticsWithConsistency { + + /** + * Define the target type fields should be mapped to.
+ * 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}. + */ + FindByAnalyticsWithConsistency as(Class returnType); + } + + interface ReactiveFindByAnalytics extends FindByAnalyticsWithProjection, FindByAnalyticsConsistentWith {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java index d3205df94..bb04bc35d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java @@ -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,6 +15,7 @@ */ package org.springframework.data.couchbase.core; +import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -36,38 +37,58 @@ public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate t @Override public ReactiveFindByAnalytics findByAnalytics(final Class domainType) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, domainType, ALL_QUERY, + null, null, null, null); } static class ReactiveFindByAnalyticsSupport implements ReactiveFindByAnalytics { private final ReactiveCouchbaseTemplate template; - private final Class domainType; + private final Class domainType; + private final Class returnType; private final AnalyticsQuery query; private final AnalyticsScanConsistency scanConsistency; + private final String scope; + private final String collection; + private final AnalyticsOptions options; - ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency) { + ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class domainType, + final Class returnType, final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency, + String scope, String collection, AnalyticsOptions options) { this.template = template; this.domainType = domainType; + this.returnType = returnType; this.query = query; this.scanConsistency = scanConsistency; + this.scope = scope; + this.collection = collection; + this.options = options; } @Override public TerminatingFindByAnalytics matching(AnalyticsQuery query) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); } @Override @Deprecated public FindByAnalyticsWithQuery consistentWith(AnalyticsScanConsistency scanConsistency) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); } @Override public FindByAnalyticsWithQuery withConsistency(AnalyticsScanConsistency scanConsistency) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); + } + + @Override + public FindByAnalyticsWithConsistency as(final Class returnType) { + Assert.notNull(returnType, "returnType must not be null!"); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); } @Override @@ -96,7 +117,7 @@ public Flux all() { long cas = row.getLong("__cas"); row.removeKey("__id"); row.removeKey("__cas"); - return template.support().decodeEntity(id, row.toString(), cas, domainType); + return template.support().decodeEntity(id, row.toString(), cas, returnType); }); }); } @@ -121,6 +142,27 @@ public Mono exists() { return count().map(count -> count > 0); } + @Override + public TerminatingFindByAnalytics withOptions(final AnalyticsOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options); + } + + @Override + public FindByAnalyticsInCollection 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); + } + + @Override + public FindByAnalyticsWithConsistency 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); + } + private String assembleEntityQuery(final boolean count) { final String bucket = "`" + template.getBucketName() + "`"; diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java index 810496e53..b31c72672 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java @@ -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 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 { */ ReactiveFindById findById(Class domainType); + /** + * Terminating operations invoking the actual execution. + * + * @param the entity type to use for the results. + */ interface TerminatingFindById extends OneAndAllIdReactive { /** @@ -53,27 +68,67 @@ interface TerminatingFindById extends OneAndAllIdReactive { } - interface FindByIdWithCollection extends TerminatingFindById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface FindByIdWithOptions extends TerminatingFindById, WithGetOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options options to use for execution + */ + @Override + TerminatingFindById withOptions(GetOptions options); + } + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindByIdInCollection extends FindByIdWithOptions, InCollection { /** - * Allows to specify a different collection than the default one configured. + * With a different collection * - * @param collection the collection to use in this scope. + * @param collection the collection to use. */ - TerminatingFindById inCollection(String collection); + @Override + FindByIdWithOptions inCollection(String collection); } - interface FindByIdWithProjection extends FindByIdWithCollection, WithProjectionId { + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindByIdInScope extends FindByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindByIdInCollection inScope(String scope); + } + + interface FindByIdWithProjection extends FindByIdInScope, WithProjectionId { /** * Load only certain fields for the document. * * @param fields the projected fields to load. */ - FindByIdWithCollection project(String... fields); + FindByIdInCollection project(String... fields); } + /** + * Provides methods for constructing query operations in a fluent way. + * + * @param the entity type to use for the results + */ interface ReactiveFindById extends FindByIdWithProjection {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 0cff986be..96293e688 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -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. @@ -24,6 +24,9 @@ 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,38 +37,50 @@ 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 ReactiveFindById findById(Class domainType) { - return new ReactiveFindByIdSupport<>(template, domainType, null, null); + return new ReactiveFindByIdSupport<>(template, domainType, null, null, null, null); } static class ReactiveFindByIdSupport implements ReactiveFindById { private final ReactiveCouchbaseTemplate template; private final Class domainType; + private final String scope; private final String collection; + private final GetOptions options; private final List fields; - ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String collection, - List fields) { + ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String scope, String collection, + GetOptions options, List fields) { this.template = template; this.domainType = domainType; + this.scope = scope; this.collection = collection; + this.options = options; this.fields = fields; } @Override public Mono one(final String id) { return Mono.just(id).flatMap(docId -> { - GetOptions options = getOptions().transcoder(RawJsonTranscoder.INSTANCE); + GetOptions gOptions = options != null ? options : getOptions(); + if (gOptions.build().transcoder() == null) { + gOptions.transcoder(RawJsonTranscoder.INSTANCE); + } if (fields != null && !fields.isEmpty()) { - options.project(fields); + gOptions.project(fields); } - return template.getCollection(collection).reactive().get(docId, options); + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, gOptions); + LOG.debug("statement: {} scope: {} collection: {}", "findById", pArgs.getScope(), pArgs.getCollection()); + return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) + .reactive().get(docId, pArgs.getOptions()); }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType)) .onErrorResume(throwable -> { if (throwable instanceof RuntimeException) { @@ -89,15 +104,27 @@ public Flux all(final Collection ids) { } @Override - public TerminatingFindById inCollection(final String collection) { + public TerminatingFindById withOptions(final GetOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields); + } + + @Override + public FindByIdWithOptions inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveFindByIdSupport<>(template, domainType, collection, fields); + return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields); + } + + @Override + public FindByIdInCollection inScope(final String scope) { + Assert.hasText(scope, "Scope must not be null nor empty."); + return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields); } @Override - public FindByIdWithCollection project(String... fields) { + public FindByIdInScope project(String... fields) { Assert.notEmpty(fields, "Fields must not be null nor empty."); - return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields)); + return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields)); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java index 7c670bad1..de056de02 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -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 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
+ * Queries the N1QL service. * * @author Michael Nitschinger * @author Michael Reiche @@ -39,7 +42,7 @@ 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 @@ interface TerminatingFindByQuery extends OneAndAllReactive { */ Mono 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 the entity type to use for the results. */ interface FindByQueryWithQuery extends TerminatingFindByQuery, WithQuery { /** - * 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 @@ interface FindByQueryWithQuery extends TerminatingFindByQuery, WithQuery matching(QueryCriteriaDefinition criteria) { @@ -118,27 +122,42 @@ default TerminatingFindByQuery matching(QueryCriteriaDefinition criteria) { } /** - * Collection override (optional). + * Fluent method to specify options. + * + * @param the entity type to use for the results. */ - interface FindByQueryInCollection extends FindByQueryWithQuery, WithCollection { - + interface FindByQueryWithOptions extends FindByQueryWithQuery, WithQueryOptions { /** - * Explicitly set the name of the collection to perform the query on.
- * 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 inCollection(String collection); + TerminatingFindByQuery withOptions(QueryOptions options); } /** - * @deprecated - * @see FindByQueryWithConsistency + * Fluent method to specify the collection + * + * @param the entity type to use for the results. + */ + interface FindByQueryInCollection extends FindByQueryWithOptions, InCollection { + FindByQueryWithOptions inCollection(String collection); + } + + /** + * Fluent method to specify the scope + * + * @param the entity type to use for the results. + */ + interface FindByQueryInScope extends FindByQueryInCollection, InScope { + FindByQueryInCollection inScope(String scope); + } + + /** + * To be removed at the next major release. use WithConsistency instead + * + * @param the entity type to use for the results. */ @Deprecated - interface FindByQueryConsistentWith extends FindByQueryInCollection { + interface FindByQueryConsistentWith extends FindByQueryInScope { /** * Allows to override the default scan consistency. @@ -146,10 +165,15 @@ interface FindByQueryConsistentWith extends FindByQueryInCollection { * @param scanConsistency the custom scan consistency to use for this query. */ @Deprecated - FindByQueryInCollection consistentWith(QueryScanConsistency scanConsistency); + FindByQueryInScope consistentWith(QueryScanConsistency scanConsistency); } + /** + * Fluent method to specify scan consistency. Scan consistency may also come from an annotation. + * + * @param the entity type to use for the results. + */ interface FindByQueryWithConsistency extends FindByQueryConsistentWith, WithConsistency { /** @@ -162,7 +186,9 @@ interface FindByQueryWithConsistency extends FindByQueryConsistentWith, Wi } /** - * Result type override (optional). + * Fluent method to specify a return type different than the the entity type to use for the results. + * + * @param the entity type to use for the results. */ interface FindByQueryWithProjection extends FindByQueryWithConsistency { @@ -178,9 +204,9 @@ interface FindByQueryWithProjection extends FindByQueryWithConsistency { } /** - * Distinct Find support. + * Fluent method to specify DISTINCT fields * - * @author Michael Reiche + * @param the entity type to use for the results. */ interface FindByQueryWithDistinct extends FindByQueryWithProjection, WithDistinct { @@ -194,6 +220,11 @@ interface FindByQueryWithDistinct extends FindByQueryWithProjection, WithD FindByQueryWithProjection distinct(String[] distinctFields); } + /** + * provides methods for constructing query operations in a fluent way. + * + * @param the entity type to use for the results + */ interface ReactiveFindByQuery extends FindByQueryWithDistinct {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index d9cd78af3..55dc5549c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -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,10 +18,14 @@ 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.java.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.ReactiveQueryResult; @@ -37,14 +41,15 @@ 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 ReactiveFindByQuery findByQuery(final Class domainType) { - return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY, - QueryScanConsistency.NOT_BOUNDED, null, null); + return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY, null, null, null, null, null); } static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { @@ -55,11 +60,16 @@ static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { 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:.) + // this is not the options argument in save( entity, options ). That becomes query.getCouchbaseOptions() + private final QueryOptions options; ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final Class returnType, final Query query, final QueryScanConsistency scanConsistency, - final String collection, final String[] distinctFields) { + final Class returnType, final Query query, final QueryScanConsistency scanConsistency, final String scope, + final String collection, final QueryOptions options, final String[] distinctFields) { Assert.notNull(domainType, "domainType must not be null!"); Assert.notNull(returnType, "returnType must not be null!"); @@ -68,7 +78,9 @@ static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { this.returnType = returnType; this.query = query; this.scanConsistency = scanConsistency; + this.scope = scope; this.collection = collection; + this.options = options; this.distinctFields = distinctFields; } @@ -80,42 +92,56 @@ public FindByQueryWithQuery matching(Query query) { } else { scanCons = scanConsistency; } - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, scope, collection, + options, distinctFields); + } + + @Override + public TerminatingFindByQuery withOptions(final QueryOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override - public FindByQueryInCollection inCollection(String collection) { + public FindByQueryInCollection 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); + } + + @Override + public FindByQueryWithConsistency inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override @Deprecated public FindByQueryConsistentWith consistentWith(QueryScanConsistency scanConsistency) { - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override public FindByQueryWithConsistency withConsistency(QueryScanConsistency scanConsistency) { - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override public FindByQueryWithConsistency as(Class returnType) { Assert.notNull(returnType, "returnType must not be null!"); - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override public FindByQueryWithDistinct distinct(String[] distinctFields) { Assert.notNull(distinctFields, "distinctFields must not be null!"); - return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, + collection, options, distinctFields); } @Override @@ -131,12 +157,14 @@ public Mono first() { @Override public Flux all() { return Flux.defer(() -> { - String statement = assembleEntityQuery(false, distinctFields); - Mono allResult = this.collection == null + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, options); + String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection()); + LOG.debug("statement: {} {}", "findByQuery", statement); + Mono 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); @@ -157,15 +185,23 @@ public Flux all() { }); } + @Override + public QueryOptions buildOptions(QueryOptions options) { + QueryOptions opts = query.buildQueryOptions(options, scanConsistency); + return opts; + } + @Override public Mono count() { return Mono.defer(() -> { - String statement = assembleEntityQuery(true, distinctFields); + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, options); + String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection()); + LOG.debug("statement: {} {}", "findByQuery", statement); Mono 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); @@ -180,12 +216,11 @@ public Mono count() { @Override public Mono 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); } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java index 9de6e2a20..f027cefe6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java @@ -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 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; + +/** + * Query 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. + */ ReactiveFindFromReplicasById findFromReplicasById(Class domainType); + /** + * Terminating operations invoking the actual get execution. + */ interface TerminatingFindFromReplicasById extends AnyIdReactive { + /** + * Finds one document based on the given ID. + * + * @param id the document ID. + * @return the entity if found. + */ Mono 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 any(Collection ids); } - interface FindFromReplicasByIdWithCollection extends TerminatingFindFromReplicasById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdWithOptions extends TerminatingFindFromReplicasById, WithGetAnyReplicaOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options options to use for execution + */ + @Override + TerminatingFindFromReplicasById withOptions(GetAnyReplicaOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdInCollection extends FindFromReplicasByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + FindFromReplicasByIdWithOptions inCollection(String collection); + } - TerminatingFindFromReplicasById inCollection(String collection); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface FindFromReplicasByIdInScope extends FindFromReplicasByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + FindFromReplicasByIdInCollection inScope(String scope); } - interface ReactiveFindFromReplicasById extends FindFromReplicasByIdWithCollection {} + /** + * Provides methods for constructing get operations in a fluent way. + * + * @param the entity type to use for the results + */ + interface ReactiveFindFromReplicasById extends FindFromReplicasByIdInScope {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java index bc9e9d7ef..44a9f6b7c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java @@ -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. @@ -17,12 +17,15 @@ import static com.couchbase.client.java.kv.GetAnyReplicaOptions.getAnyReplicaOptions; +import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Collection; -import org.springframework.util.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.core.support.PseudoArgs; import com.couchbase.client.java.codec.RawJsonTranscoder; import com.couchbase.client.java.kv.GetAnyReplicaOptions; @@ -37,29 +40,40 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin @Override public ReactiveFindFromReplicasById findFromReplicasById(Class domainType) { - return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null, null, null); } static class ReactiveFindFromReplicasByIdSupport implements ReactiveFindFromReplicasById { + private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindFromReplicasByIdOperationSupport.class); private final ReactiveCouchbaseTemplate template; private final Class domainType; private final Class returnType; + private final String scope; private final String collection; + private final GetAnyReplicaOptions options; ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, Class returnType, - String collection) { + String scope, String collection, GetAnyReplicaOptions options) { this.template = template; this.domainType = domainType; this.returnType = returnType; + this.scope = scope; this.collection = collection; + this.options = options; } @Override public Mono 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 pArgs = new PseudoArgs<>(template, scope, collection, garOptions); + LOG.debug("statement: {} scope: {} collection: {}", "getAnyReplica", pArgs.getScope(), pArgs.getCollection()); + return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) + .reactive().getAnyReplica(docId, pArgs.getOptions()); }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), returnType)) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { @@ -76,9 +90,21 @@ public Flux any(Collection ids) { } @Override - public TerminatingFindFromReplicasById inCollection(final String collection) { + public TerminatingFindFromReplicasById withOptions(final GetAnyReplicaOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); + } + + @Override + public FindFromReplicasByIdWithOptions inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); + } + + @Override + public FindFromReplicasByIdInCollection inScope(final String scope) { + Assert.hasText(scope, "Scope must not be null nor empty."); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java index 943a14bf3..b7886c1ad 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java @@ -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 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. + */ ReactiveInsertById insertById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingInsertById extends OneAndAllEntityReactive { + /** + * Insert one entity. + * + * @return Inserted entity. + */ + @Override Mono one(T object); + /** + * Insert a collection of entities. + * + * @return Inserted entities + */ + @Override Flux all(Collection objects); + InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc); + } - interface InsertByIdWithCollection extends TerminatingInsertById, WithCollection { + /** + * Fluent method to specify options. + */ + interface InsertByIdWithOptions extends TerminatingInsertById, WithInsertOptions { + /** + * Fluent method to specify options to use for execution. + * + * @param options to use for execution + */ + @Override + TerminatingInsertById withOptions(InsertOptions options); + } + + /** + * Fluent method to specify the collection. + */ + interface InsertByIdInCollection extends InsertByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + InsertByIdWithOptions inCollection(String collection); + } - TerminatingInsertById inCollection(String collection); + /** + * Fluent method to specify the scope. + */ + interface InsertByIdInScope extends InsertByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + InsertByIdInCollection inScope(String scope); } - interface InsertByIdWithDurability extends InsertByIdWithCollection, WithDurability { + interface InsertByIdWithDurability extends InsertByIdInScope, WithDurability { - InsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + @Override + InsertByIdInCollection withDurability(DurabilityLevel durabilityLevel); - InsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + @Override + InsertByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); } interface InsertByIdWithExpiry extends InsertByIdWithDurability, WithExpiry { + @Override InsertByIdWithDurability withExpiry(Duration expiry); } + /** + * Provides methods for constructing KV insert operations in a fluent way. + * + * @param the entity type to insert + */ interface ReactiveInsertById extends InsertByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 3898fcd82..20caa8f48 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -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,8 +21,10 @@ 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 @@ 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 ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate templa @Override public ReactiveInsertById insertById(final Class 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); } @@ -49,18 +52,22 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { private final ReactiveCouchbaseTemplate template; private final Class 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; - ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String scope, + final String collection, final InsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, 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; @@ -71,8 +78,13 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { public Mono one(T object) { return Mono.just(object).flatMap(o -> { CouchbaseDocument converted = template.support().encodeEntity(o); - return template.getCollection(collection).reactive() - .insert(converted.getId(), converted.export(), buildInsertOptions(converted)).map(result -> { + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, + options != null ? options : InsertOptions.insertOptions()); + LOG.debug("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(), + pArgs.getCollection(), pArgs.getOptions()); + return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) + .reactive().insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted)) + .map(result -> { Object updatedObject = template.support().applyUpdatedId(o, converted.getId()); return (T) template.support().applyUpdatedCas(updatedObject, result.cas()); }); @@ -90,8 +102,9 @@ public Flux all(Collection objects) { 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) { @@ -106,32 +119,46 @@ private InsertOptions buildInsertOptions(CouchbaseDocument doc) { // CouchbaseDo } @Override - public TerminatingInsertById inCollection(final String collection) { + public TerminatingInsertById withOptions(final InsertOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); + } + + @Override + public InsertByIdInCollection 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); + } + + @Override + public InsertByIdWithOptions inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override - public InsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + public InsertByIdInCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); - return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override - public InsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public InsertByIdInCollection 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); + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override public InsertByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); - return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java index e8af89b3f..300203fb4 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java @@ -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 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 { + /** + * Remove one document based on the given ID. + * + * @param id the document ID. + * @return result of the remove + */ + @Override Mono one(String id); + /** + * Remove the documents in the collection. + * + * @param ids the document IDs. + * @return result of the removes. + */ + @Override Flux all(Collection ids); } - interface RemoveByIdWithCollection extends TerminatingRemoveById, WithCollection { - - TerminatingRemoveById inCollection(String collection); - + /** + * Fluent method to specify options. + */ + interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions { + /** + * 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 { + /** + * Fluent method to specify the collection. + */ + interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection { + /** + * 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 { + /** + * 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 { + @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 {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index 4b33c7437..8c7dcc51f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -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 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 ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate templa @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 @@ static class ReactiveRemoveByIdSupport implements ReactiveRemoveById { @Override public Mono 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 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 Flux all(final Collection ids) { 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) { @@ -89,29 +101,46 @@ private RemoveOptions buildRemoveOptions() { return options; } + @Override + public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo, + durabilityLevel, cas); + } + + @Override + 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, scope, collection, options, persistTo, replicateTo, + durabilityLevel, cas); + } + @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); + return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo, + durabilityLevel, cas); } @Override - public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { - Assert.notNull(durabilityLevel, "Durability Level must not be null."); - return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, null); + 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 RemoveByIdWithCollection 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); + 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(final Long cas) { - Assert.notNull(cas, "CAS must not be null."); - return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel, cas); + public RemoveByIdWithDurability withCas(Long cas) { + return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo, + durabilityLevel, cas); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java index 7eb7b36d9..7619eabf1 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java @@ -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 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. + */ ReactiveRemoveByQuery removeByQuery(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingRemoveByQuery { + /** + * Remove all matching documents. + * + * @return RemoveResult for each matching document + */ Flux all(); } + /** + * Fluent methods to specify the query + * + * @param the entity type. + */ interface RemoveByQueryWithQuery extends TerminatingRemoveByQuery, WithQuery { TerminatingRemoveByQuery matching(Query query); @@ -42,25 +67,67 @@ default TerminatingRemoveByQuery matching(QueryCriteriaDefinition criteria) { } } - interface RemoveByQueryInCollection extends RemoveByQueryWithQuery, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryWithOptions extends RemoveByQueryWithQuery, WithQueryOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + RemoveByQueryWithQuery withOptions(QueryOptions options); + } - RemoveByQueryWithQuery inCollection(String collection); + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryInCollection extends RemoveByQueryWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + RemoveByQueryWithOptions inCollection(String collection); + } + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface RemoveByQueryInScope extends RemoveByQueryInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + RemoveByQueryInCollection inScope(String scope); } @Deprecated - interface RemoveByQueryConsistentWith extends RemoveByQueryInCollection { + interface RemoveByQueryConsistentWith extends RemoveByQueryInScope { + @Deprecated - RemoveByQueryInCollection consistentWith(QueryScanConsistency scanConsistency); + RemoveByQueryInScope consistentWith(QueryScanConsistency scanConsistency); } interface RemoveByQueryWithConsistency extends RemoveByQueryConsistentWith, WithConsistency { - + @Override RemoveByQueryConsistentWith withConsistency(QueryScanConsistency scanConsistency); } + /** + * Provides methods for constructing query operations in a fluent way. + * + * @param the entity type. + */ interface ReactiveRemoveByQuery extends RemoveByQueryWithConsistency {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index 3a76a2817..63be8fdb2 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -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 ReactiveRemoveByQuery removeByQuery(Class 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 implements ReactiveRemoveByQuery { @@ -49,24 +55,32 @@ static class ReactiveRemoveByQuerySupport implements ReactiveRemoveByQuery private final Class 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 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 all() { return Flux.defer(() -> { - String statement = assembleDeleteQuery(); - Mono allResult = this.collection == null - ? template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions()) - : template.getCouchbaseClientFactory().getScope().reactive().query(statement, buildQueryOptions()); + PseudoArgs pArgs = new PseudoArgs<>(template, scope, collection, options); + String statement = assembleDeleteQuery(pArgs.getCollection()); + LOG.debug("statement: {}", statement); + Mono 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,8 +93,8 @@ public Flux all() { }); } - private QueryOptions buildQueryOptions() { - final QueryOptions options = QueryOptions.queryOptions(); + private QueryOptions buildQueryOptions(QueryOptions options) { + options = options != null ? options : QueryOptions.queryOptions(); if (scanConsistency != null) { options.scanConsistency(scanConsistency); } @@ -89,30 +103,47 @@ private QueryOptions buildQueryOptions() { @Override public TerminatingRemoveByQuery 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 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 consistentWith(final QueryScanConsistency scanConsistency) { - return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); + public RemoveByQueryInScope consistentWith(final QueryScanConsistency scanConsistency) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, + options); } @Override public RemoveByQueryConsistentWith 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 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 inScope(final String scope) { + Assert.hasText(scope, "Scope must not be null nor empty."); + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, + options); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java index ded3a38ca..6b4c5fdb8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java @@ -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 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; +/** + * Insert Operations + * + * @author Christoph Strobl + * @since 2.0 + */ public interface ReactiveReplaceByIdOperation { + /** + * Replace using the KV service. + * + * @param domainType the entity type to replace. + */ ReactiveReplaceById replaceById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingReplaceById extends OneAndAllEntityReactive { + /** + * Replace one entity. + * + * @return Replaced entity. + */ Mono one(T object); + /** + * Replace a collection of entities. + * + * @return Replaced entities + */ Flux all(Collection objects); } - interface ReplaceByIdWithCollection extends TerminatingReplaceById, WithCollection { + /** + * Fluent method to specify options. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdWithOptions extends TerminatingReplaceById, WithReplaceOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingReplaceById withOptions(ReplaceOptions options); + } + + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdInCollection extends ReplaceByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + ReplaceByIdWithOptions inCollection(String collection); + } - TerminatingReplaceById inCollection(String collection); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface ReplaceByIdInScope extends ReplaceByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + ReplaceByIdInCollection inScope(String scope); } - interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection, WithDurability { + interface ReplaceByIdWithDurability extends ReplaceByIdInScope, WithDurability { - ReplaceByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + ReplaceByIdInCollection withDurability(DurabilityLevel durabilityLevel); - ReplaceByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + ReplaceByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); } @@ -58,6 +125,11 @@ interface ReplaceByIdWithExpiry extends ReplaceByIdWithDurability, WithExp ReplaceByIdWithDurability withExpiry(final Duration expiry); } + /** + * Provides methods for constructing KV replace operations in a fluent way. + * + * @param the entity type to replace + */ interface ReactiveReplaceById extends ReplaceByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index 86442c12a..c5e0e3fa1 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -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,7 +21,10 @@ 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 @@ 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 ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate templ @Override public ReactiveReplaceById replaceById(final Class 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); } @@ -48,18 +52,22 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { private final ReactiveCouchbaseTemplate template; private final Class 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; - ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String scope, + final String collection, final 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; @@ -68,10 +76,14 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { @Override public Mono one(T object) { + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, + options != null ? options : ReplaceOptions.replaceOptions()); + LOG.debug("statement: {} pArgs: {}", "replaceById", pArgs); return Mono.just(object).flatMap(o -> { CouchbaseDocument converted = template.support().encodeEntity(o); - return template.getCollection(collection).reactive() - .replace(converted.getId(), converted.export(), buildReplaceOptions(o, converted)) + return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) + .reactive() + .replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), o, converted)) .map(result -> (T) template.support().applyUpdatedCas(o, result.cas())); }).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { @@ -87,8 +99,8 @@ public Flux all(Collection objects) { 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) { @@ -105,32 +117,46 @@ private ReplaceOptions buildReplaceOptions(T object, CouchbaseDocument doc) { } @Override - public TerminatingReplaceById inCollection(final String collection) { + public TerminatingReplaceById withOptions(final ReplaceOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveReplaceByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); + } + + @Override + public ReplaceByIdWithDurability inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveReplaceByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); + } + + @Override + public ReplaceByIdInCollection 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); } @Override - public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + public ReplaceByIdInCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override - public ReplaceByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public ReplaceByIdInCollection 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); + return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override public ReplaceByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java index 72c81800d..128f76136 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java @@ -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 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; +/** + * Insert Operations + * + * @author Christoph Strobl + * @since 2.0 + */ public interface ReactiveUpsertByIdOperation { + + /** + * Upsert using the KV service. + * + * @param domainType the entity type to upsert. + */ ReactiveUpsertById upsertById(Class domainType); + /** + * Terminating operations invoking the actual execution. + */ interface TerminatingUpsertById extends OneAndAllEntityReactive { + /** + * Upsert one entity. + * + * @return Upserted entity. + */ + @Override Mono one(T object); + /** + * Insert a collection of entities. + * + * @return Inserted entities + */ + @Override Flux all(Collection objects); } - interface UpsertByIdWithCollection extends TerminatingUpsertById, WithCollection { - - TerminatingUpsertById inCollection(String collection); + /** + * Fluent method to specify options. + * + * @param the entity type to use. + */ + interface UpsertByIdWithOptions extends TerminatingUpsertById, WithUpsertOptions { + /** + * Fluent method to specify options to use for execution + * + * @param options to use for execution + */ + @Override + TerminatingUpsertById withOptions(UpsertOptions options); } - interface UpsertByIdWithDurability extends UpsertByIdWithCollection, WithDurability { + /** + * Fluent method to specify the collection. + * + * @param the entity type to use for the results. + */ + interface UpsertByIdInCollection extends UpsertByIdWithOptions, InCollection { + /** + * With a different collection + * + * @param collection the collection to use. + */ + @Override + UpsertByIdWithOptions inCollection(String collection); + } - UpsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + /** + * Fluent method to specify the scope. + * + * @param the entity type to use for the results. + */ + interface UpsertByIdInScope extends UpsertByIdInCollection, InScope { + /** + * With a different scope + * + * @param scope the scope to use. + */ + @Override + UpsertByIdInCollection inScope(String scope); + } - UpsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + interface UpsertByIdWithDurability extends UpsertByIdInScope, WithDurability { + @Override + UpsertByIdInCollection withDurability(DurabilityLevel durabilityLevel); + @Override + UpsertByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); } interface UpsertByIdWithExpiry extends UpsertByIdWithDurability, WithExpiry { - + @Override UpsertByIdWithDurability withExpiry(Duration expiry); } + /** + * Provides methods for constructing KV operations in a fluent way. + * + * @param the entity type to upsert + */ interface ReactiveUpsertById extends UpsertByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java index 8de91df0a..9c05ed930 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -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,6 +22,7 @@ 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 ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate templa @Override public ReactiveUpsertById upsertById(final Class 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); } @@ -48,18 +49,22 @@ static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { private final ReactiveCouchbaseTemplate template; private final Class 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; - ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class 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; @@ -70,8 +75,13 @@ static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { public Mono one(T object) { return Mono.just(object).flatMap(o -> { CouchbaseDocument converted = template.support().encodeEntity(o); - return template.getCollection(collection).reactive() - .upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).map(result -> { + // using collection works fine for collection == null or "_default", when scope is not set in clientFactory + // but if scope is set, then the collection must be specified as well. + PseudoArgs pArgs = new PseudoArgs(template, scope, collection, + options != null ? options : UpsertOptions.upsertOptions()); + return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()) + .reactive().upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted)) + .map(result -> { Object updatedObject = template.support().applyUpdatedId(o, converted.getId()); return (T) template.support().applyUpdatedCas(updatedObject, result.cas()); }); @@ -89,8 +99,8 @@ public Flux all(Collection objects) { 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 +115,46 @@ private UpsertOptions buildUpsertOptions(CouchbaseDocument doc) { } @Override - public TerminatingUpsertById inCollection(final String collection) { + public TerminatingUpsertById withOptions(final UpsertOptions options) { + Assert.notNull(options, "Options must not be null."); + return new ReactiveUpsertByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); + } + + @Override + public UpsertByIdWithDurability inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); + } + + @Override + public UpsertByIdInCollection 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); } @Override - public UpsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + public UpsertByIdInCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); - return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override - public UpsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + public UpsertByIdInCollection 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); + return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } @Override public UpsertByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); - return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + durabilityLevel, expiry); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java index fc60afbc6..acbc7e822 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java @@ -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.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. *

@@ -98,13 +99,22 @@ public String getFieldName() { // 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 isExpiration = Lazy.of(() -> { + return this.isAnnotationPresent(Expiration.class); + }); + } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java index e5350b58f..94b3cb28d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java @@ -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 - the entity class + */ +public interface InCollection { + /** + * Specify collection + * + * @param collection - collection name + */ + Object inCollection(String collection); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithCollection.java b/src/main/java/org/springframework/data/couchbase/core/support/InScope.java similarity index 80% rename from src/main/java/org/springframework/data/couchbase/core/support/WithCollection.java rename to src/main/java/org/springframework/data/couchbase/core/support/InScope.java index 7482365d8..36506c698 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithCollection.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/InScope.java @@ -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 - the entity class */ -public interface WithCollection { - Object inCollection(String collectionName); +public interface InScope { + /** + * Specify scope + * + * @param scope - scope name + */ + Object inScope(String scope); } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java b/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java new file mode 100644 index 000000000..f60127899 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java @@ -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 { + 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
+ * 1) values from fluent api
+ * 2) values from dynamic proxy (via template threadLocal)
+ * 3) the values from the couchbaseClientFactory
+ * + * @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.getThreadLocalArgs() == null ? null : (OPTS) (template.getThreadLocalArgs().getOptions()); + } + + /** + * @@return the scope name from the ThreadLocal field of the template + */ + private String getThreadLocalScopeName(ReactiveCouchbaseTemplate template) { + return template.getThreadLocalArgs() == null ? null : template.getThreadLocalArgs().getScope(); + } + + /** + * @@return the collection name from the ThreadLocal field of the template + */ + private String getThreadLocalCollectionName(ReactiveCouchbaseTemplate template) { + return template.getThreadLocalArgs() == null ? null : template.getThreadLocalArgs().getCollection(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsConsistency.java b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsConsistency.java index 93aa8f825..60f6b2b01 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsConsistency.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsConsistency.java @@ -18,11 +18,16 @@ 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 - the entity class */ public interface WithAnalyticsConsistency { + /** + * Specify scan consistency + * + * @param scanConsistency - scan consistency + */ Object withConsistency(AnalyticsScanConsistency scanConsistency); } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsOptions.java new file mode 100644 index 000000000..d040ccfbf --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsOptions.java @@ -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 - the entity class + */ +public interface WithAnalyticsOptions { + /** + * Specify analytics options + * + * @param options - analytics options + */ + Object withOptions(AnalyticsOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsQuery.java b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsQuery.java index 82f18e2ab..27c5a6cab 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsQuery.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithAnalyticsQuery.java @@ -18,11 +18,16 @@ 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 - the entity class */ public interface WithAnalyticsQuery { + /** + * Specify query + * + * @param query - query + */ Object matching(AnalyticsQuery query); } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithConsistency.java b/src/main/java/org/springframework/data/couchbase/core/support/WithConsistency.java index 01189f207..8a7c7cab7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithConsistency.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithConsistency.java @@ -18,11 +18,16 @@ 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 - the entity class */ public interface WithConsistency { + /** + * Specify scan consistency + * + * @param scanConsistency - scan consistency + */ Object withConsistency(QueryScanConsistency scanConsistency); } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java b/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java index b7b2f629a..d5c6aba5d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java @@ -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 - the entity class */ public interface WithDistinct { + /** + * Specify distinct field names + * + * @param distinctFields - distinct fields + */ Object distinct(String[] distinctFields); } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithExistsOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithExistsOptions.java new file mode 100644 index 000000000..589153476 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithExistsOptions.java @@ -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 - the entity class + */ +public interface WithExistsOptions { + /** + * Specify options + * + * @param options - exists options + */ + Object withOptions(ExistsOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithGetAnyReplicaOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithGetAnyReplicaOptions.java new file mode 100644 index 000000000..8a0871996 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithGetAnyReplicaOptions.java @@ -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 - the entity class + */ +public interface WithGetAnyReplicaOptions { + /** + * Specify options + * + * @param options - GetAnyReplicaOptions + */ + Object withOptions(GetAnyReplicaOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithGetOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithGetOptions.java new file mode 100644 index 000000000..d472c7c79 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithGetOptions.java @@ -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 - the entity class + */ +public interface WithGetOptions { + /** + * Specify options + * + * @param options - get options + */ + Object withOptions(GetOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithInsertOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithInsertOptions.java new file mode 100644 index 000000000..e338e42cb --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithInsertOptions.java @@ -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 - the entity class + */ +public interface WithInsertOptions { + /** + * Specify options + * + * @param options - insert options + */ + Object withOptions(InsertOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithQueryOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithQueryOptions.java new file mode 100644 index 000000000..b78066b17 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithQueryOptions.java @@ -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 - the entity class + */ +public interface WithQueryOptions { + /** + * Specify options + * + * @param options - query options + */ + Object withOptions(QueryOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithRemoveOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithRemoveOptions.java new file mode 100644 index 000000000..ea7740627 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithRemoveOptions.java @@ -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 - the entity class + */ +public interface WithRemoveOptions { + Object withOptions(RemoveOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithReplaceOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithReplaceOptions.java new file mode 100644 index 000000000..340d00670 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithReplaceOptions.java @@ -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 - the entity class + */ +public interface WithReplaceOptions { + Object withOptions(ReplaceOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithUpsertOptions.java b/src/main/java/org/springframework/data/couchbase/core/support/WithUpsertOptions.java new file mode 100644 index 000000000..7eca14cec --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithUpsertOptions.java @@ -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 - the entity class + */ +public interface WithUpsertOptions { + Object withOptions(UpsertOptions options); +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java index eb55e5d49..4f49cb916 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java @@ -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 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase 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,8 @@ protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processo ExecutableFindByQuery find = typeToRead == null ? findOperationWithProjection // : findOperationWithProjection; // not yet implemented in core .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented + // TODO (maybe) // method.getEntityInformation().getCollectionName(); // not yet implemented + String collection = CollectionIdentifier.DEFAULT_SCOPE + "." + CollectionIdentifier.DEFAULT_COLLECTION; CouchbaseQueryExecution execution = getExecution(accessor, new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find); @@ -101,7 +103,7 @@ protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processo * @return */ private CouchbaseQueryExecution getExecution(ParameterAccessor accessor, Converter resultProcessing, - ExecutableFindByQueryOperation.ExecutableFindByQuery operation) { + ExecutableFindByQuery operation) { return new CouchbaseQueryExecution.ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing); } @@ -114,7 +116,7 @@ private CouchbaseQueryExecution getExecution(ParameterAccessor accessor, Convert * @return */ private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, - ExecutableFindByQueryOperation.ExecutableFindByQuery operation) { + ExecutableFindByQuery operation) { if (isDeleteQuery()) { return new DeleteExecution(getOperations(), getQueryMethod()); @@ -130,7 +132,7 @@ private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, 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(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java index 852bd0d3d..9314371d1 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java @@ -20,7 +20,7 @@ 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 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; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java index 2a23615c8..e2da9343b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java @@ -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,8 @@ protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processo ? findOperationWithProjection // : findOperationWithProjection; // note yet implemented in core .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented + // TODO (maybe) // method.getEntityInformation().getCollectionName(); // not yet implemented + String collection = CollectionIdentifier.DEFAULT_SCOPE + "." + CollectionIdentifier.DEFAULT_COLLECTION; ReactiveCouchbaseQueryExecution execution = getExecution(accessor, new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java index c64054b24..ba04f2878 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java @@ -19,7 +19,8 @@ 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 @@ public Object execute(Query query, Class type, String collection) { */ 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 @@ public Object execute(Query query, Class type, String collection) { */ final class PagedExecution 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 @@ public PagedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery op @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 diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java index c7fd87bd4..313737e97 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java @@ -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. @@ -35,12 +35,12 @@ public class CouchbaseRepositoryQuery implements RepositoryQuery { private final QueryMethodEvaluationContextProvider evaluationContextProvider; public CouchbaseRepositoryQuery(final CouchbaseOperations operations, final CouchbaseQueryMethod queryMethod, - final NamedQueries namedQueries, final QueryMethodEvaluationContextProvider evaluationContextProvider) { + final NamedQueries namedQueries) { this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; - this.evaluationContextProvider = evaluationContextProvider; - throw new RuntimeException("Deprecated"); + this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT; + //throw new RuntimeException("Deprecated"); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java index 384106335..a6114ec22 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java @@ -71,9 +71,23 @@ protected QueryCriteria create(final Part part, final Iterator iterator) return from(part, property, where(addMetaIfRequired(path, property)), iterator); } - static Converter cvtr = ( - source) -> new StringBuilder(source.getFieldName().length() + 2).append('`').append(source.getFieldName()) - .append('`').toString(); + static Converter cvtr = new MyConverter(); + + static class MyConverter implements Converter { + @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 iterator) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java index 4cbf6c9de..a72b374f4 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java @@ -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; @@ -47,7 +47,7 @@ public N1qlRepositoryQueryExecutor(final CouchbaseOperations operations, final C this.queryMethod = queryMethod; this.namedQueries = namedQueries; this.evaluationContextProvider = evaluationContextProvider; - throw new RuntimeException("Deprecated"); + //throw new RuntimeException("Deprecated"); } private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); @@ -64,7 +64,7 @@ public Object execute(final Object[] parameters) { // 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(); @@ -73,7 +73,7 @@ public Object execute(final Object[] parameters) { 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(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseRepositoryQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseRepositoryQuery.java index 5e87ab56e..f2bcca1d3 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseRepositoryQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseRepositoryQuery.java @@ -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. @@ -36,14 +36,13 @@ public class ReactiveCouchbaseRepositoryQuery extends AbstractReactiveCouchbaseQ private final QueryMethodEvaluationContextProvider evaluationContextProvider; public ReactiveCouchbaseRepositoryQuery(final ReactiveCouchbaseOperations operations, - final ReactiveCouchbaseQueryMethod queryMethod, final NamedQueries namedQueries, - final QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(queryMethod, operations, new SpelExpressionParser(), evaluationContextProvider); + final ReactiveCouchbaseQueryMethod queryMethod, final NamedQueries namedQueries) { + super(queryMethod, operations, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT); this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; - this.evaluationContextProvider = evaluationContextProvider; - throw new RuntimeException("Deprecated"); + this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT; + //throw new RuntimeException("Deprecated"); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveN1qlRepositoryQueryExecutor.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveN1qlRepositoryQueryExecutor.java index 084f1684d..2ff79c091 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveN1qlRepositoryQueryExecutor.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveN1qlRepositoryQueryExecutor.java @@ -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. @@ -40,7 +40,7 @@ public ReactiveN1qlRepositoryQueryExecutor(final ReactiveCouchbaseOperations ope this.queryMethod = queryMethod; this.namedQueries = namedQueries; this.evaluationContextProvider = evaluationContextProvider; - throw new RuntimeException("Deprecated"); + //throw new RuntimeException("Deprecated"); } /** diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java index d6c69ef71..745332566 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java @@ -189,7 +189,7 @@ public Mono count() { @Override public Mono deleteAll() { - return operations.removeByQuery(entityInformation.getJavaType()).all().then(); + return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all().then(); } /** diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index 9d4940d8a..f2d3fb819 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -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.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 @@ public void beforeEach() { 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 @@ void upsertAndFindById() { @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 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 @@ void withDurability() 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 @@ void upsertAndRemoveById() { { 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 @@ void insertById() { @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)); } diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java index cf5fe0154..3a56b8ffb 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java @@ -19,8 +19,10 @@ 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.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.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 @@ public void beforeEach() { 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 @@ void findByMatchingQueryProjected() { .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 @@ void removeByQueryAll() { 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 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 @@ void distinctReactive() { // 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 @@ void distinctReactive() { } } + /** + * find . -name 'Exec*OperationSupport.java'|awk -F/ '{print $NF}'|sort| awk -F. '{print "* ", NR, ")",$1, ""}'
+ * 1) ExecutableExistsByIdOperationSupport
+ * 2) ExecutableFindByAnalyticsOperationSupport
+ * 3) ExecutableFindByIdOperationSupport
+ * 4) ExecutableFindByQueryOperationSupport
+ * 5) ExecutableFindFromReplicasByIdOperationSupport
+ * 6) ExecutableInsertByIdOperationSupport
+ * 7) ExecutableRemoveByIdOperationSupport
+ * 8) ExecutableRemoveByQueryOperationSupport
+ * 9) ExecutableReplaceByIdOperationSupport
+ * 10)ExecutableUpsertByIdOperationSupport
+ */ + @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 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 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 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 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 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 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)); + } + } diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java index 1233808af..22518fdcc 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -20,8 +20,6 @@ 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 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 @@ void findByQueryAll() { 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 @@ void findByMatchingQuery() { @Test void findByMatchingQueryProjected() { + couchbaseTemplate.removeByQuery(UserSubmission.class).all(); + UserSubmission user = new UserSubmission(); user.setId(UUID.randomUUID().toString()); user.setUsername("dave"); diff --git a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java index ca1e06afd..490133e2e 100644 --- a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java @@ -219,7 +219,7 @@ void testBetween() { @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)); @@ -229,7 +229,7 @@ void testIn() { @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)); diff --git a/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java new file mode 100644 index 000000000..8c3cd9437 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/query/ReactiveCouchbaseTemplateQueryCollectionIntegrationTests.java @@ -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 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 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 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 foundUsers = couchbaseTemplate.findByQuery(User.class).as(UserJustLastName.class) + .withConsistency(QueryScanConsistency.REQUEST_PLUS).inCollection(collectionName).matching(specialUsers).all(); + assertEquals(1, foundUsers.size()); + + final List 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 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 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 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 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 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, ""}'
+ * 1) ExecutableExistsByIdOperationSupport
+ * 2) ExecutableFindByAnalyticsOperationSupport
+ * 3) ExecutableFindByIdOperationSupport
+ * 4) ExecutableFindByQueryOperationSupport
+ * 5) ExecutableFindFromReplicasByIdOperationSupport
+ * 6) ExecutableInsertByIdOperationSupport
+ * 7) ExecutableRemoveByIdOperationSupport
+ * 8) ExecutableRemoveByQueryOperationSupport
+ * 9) ExecutableReplaceByIdOperationSupport
+ * 10)ExecutableUpsertByIdOperationSupport
+ */ + @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 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 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 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 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 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 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()); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/Airport.java b/src/test/java/org/springframework/data/couchbase/domain/Airport.java index d6208eeeb..6d3cd7207 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Airport.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Airport.java @@ -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. @@ -18,6 +18,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.Version; import org.springframework.data.couchbase.core.mapping.Document; /** @@ -34,6 +35,8 @@ public class Airport extends ComparableEntity { String icao; + @Version Number version; + @PersistenceConstructor public Airport(String id, String iata, String icao) { this.id = id; @@ -53,4 +56,16 @@ public String getIcao() { return icao; } + public Airport withId(String id) { + return new Airport(id, this.iata, this.icao); + } + + public Airport withIcao(String icao) { + return new Airport(this.getId(), this.iata, icao); + } + + public Airport clearVersion() { + version = Long.valueOf(0); + return this; + } } diff --git a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java index 911aebd70..61057d401 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -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. @@ -17,32 +17,34 @@ package org.springframework.data.couchbase.domain; import java.util.List; +import java.util.Optional; +import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.couchbase.client.java.query.QueryScanConsistency; /** - * template class for Reactive Couchbase operations + * Airport repository for testing
+ * The DynamicProxyable interface exposes airportRepository.withScope(scope), withCollection() and withOptions() It's + * necessary on the repository object itself because the withScope() etc methods need to return an object of type + * AirportRepository so that one can code... airportRepository = airportRepository.withScope(scopeName) without having + * to cast the result. * * @author Michael Nitschinger * @author Michael Reiche */ @Repository -public interface AirportRepository extends PagingAndSortingRepository { +public interface AirportRepository extends CouchbaseRepository { @Override @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) - Iterable findAll(); - - @Override - Airport save(Airport airport); + List findAll(); @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List findAllByIata(String iata); @@ -50,6 +52,8 @@ public interface AirportRepository extends PagingAndSortingRepository getAllByIata(String iata); @@ -75,4 +79,8 @@ Long countFancyExpression(@Param("projectIds") List projectIds, @Param(" @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Page findAllByIataNot(String iata, Pageable pageable); + + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Optional findByIdAndIata(String id, String iata); + } diff --git a/src/test/java/org/springframework/data/couchbase/domain/Config.java b/src/test/java/org/springframework/data/couchbase/domain/Config.java index 3db6b069d..1a6a6f468 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Config.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Config.java @@ -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 @@ */ @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 String typeKey() { return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter } + static String scopeName = null; + + @Override + protected String getScopeName() { + return scopeName; + } + + public static void setScopeName(String scopeName) { + Config.scopeName = scopeName; + } + } diff --git a/src/test/java/org/springframework/data/couchbase/domain/ConfigScoped.java b/src/test/java/org/springframework/data/couchbase/domain/ConfigScoped.java deleted file mode 100644 index 9093bd143..000000000 --- a/src/test/java/org/springframework/data/couchbase/domain/ConfigScoped.java +++ /dev/null @@ -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; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/domain/CustomMappingCouchbaseConverter.java b/src/test/java/org/springframework/data/couchbase/domain/CustomMappingCouchbaseConverter.java index e17e83c29..8b2caa6fe 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/CustomMappingCouchbaseConverter.java +++ b/src/test/java/org/springframework/data/couchbase/domain/CustomMappingCouchbaseConverter.java @@ -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 @@ 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, CouchbasePersistentProperty> mappingContext, final String typeKey) { + public CustomMappingCouchbaseConverter( + final MappingContext, CouchbasePersistentProperty> mappingContext, + final String typeKey) { super(mappingContext, typeKey); this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey); } diff --git a/src/test/java/org/springframework/data/couchbase/domain/FluxTest.java b/src/test/java/org/springframework/data/couchbase/domain/FluxTest.java new file mode 100644 index 000000000..aeb5356bf --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/FluxTest.java @@ -0,0 +1,224 @@ +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) +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 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 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> listOfLists = new LinkedList<>(); + Airport a = new Airport(UUID.randomUUID().toString(), "iata", "lowp"); + String last = null; + for (int i = 0; i < 5; i++) { + LinkedList list = new LinkedList<>(); + for (int j = 0; j < 10; j++) { + list.add(a.withId(UUID.randomUUID().toString())); + last = a.getId(); + } + listOfLists.add(list); + } + Flux af = Flux.fromIterable(listOfLists).concatMap(catalogToStore -> Flux.fromIterable(catalogToStore) + .parallel(4).runOn(Schedulers.parallel()).concatMap((entity) -> airportRepository.save(entity))); + List 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 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 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 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 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 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 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 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(); + } + + } +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/Person.java b/src/test/java/org/springframework/data/couchbase/domain/Person.java index 0b4ad12a6..df64cb829 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Person.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Person.java @@ -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 @@ 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 Person(String firstname, String lastname) { this(); setFirstname(firstname); setLastname(lastname); + setMiddlename("Nick"); } public Person(int id, String firstname, String lastname) { diff --git a/src/test/java/org/springframework/data/couchbase/domain/PersonRepository.java b/src/test/java/org/springframework/data/couchbase/domain/PersonRepository.java index b5dafc153..4fdcad520 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/PersonRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/PersonRepository.java @@ -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 { void deleteAll(); - @ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS) + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List findByAddressStreet(String street); + + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + List findByMiddlename(String nickName); } diff --git a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java index 63cb3388d..1c5175906 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java @@ -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.repository.reactive.ReactiveSortingRepository; import org.springframework.stereotype.Repository; +import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.query.QueryScanConsistency; /** @@ -45,12 +47,25 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository findAll(); + @Override + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Mono deleteAll(); + @Override Mono save(Airport a); @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Flux findAllByIata(String iata); + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}") + Flux findAllPoliciesByApplicableTypes(String state, JsonArray applicableTypes); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} and icao != $1 ORDER BY effectiveDateTime DESC LIMIT 1") + Mono findPolicySnapshotByPolicyIdAndEffectiveDateTime(String policyId, long effectiveDateTime); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} ORDER BY effectiveDateTime DESC") + Flux findPolicySnapshotAll(); + @Query("#{#n1ql.selectEntity} where iata = $1") @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Flux getAllByIata(String iata); diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java index 484cd0e26..98bad0211 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -89,7 +89,7 @@ public void beforeEach() { void shouldSaveAndFindAll() { Airport vie = null; try { - vie = new Airport("airports::vie", "vie", "loww"); + vie = new Airport("airports::vie", "vie", "low4"); airportRepository.save(vie); List all = new ArrayList<>(); airportRepository.findAll().forEach(all::add); @@ -124,7 +124,7 @@ void findByInjection() { 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); @@ -144,7 +144,7 @@ void findByInjection() { 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 airports = airportRepository.findAllByIata("vie"); assertEquals(1, airports.size()); diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java index d6b582f7a..ed07c651c 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java @@ -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.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 @@ void shouldSaveAndFindAll() { 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 @@ void shouldSaveAndFindAll() { 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 airports1 = airportRepository.findAllByIata("vie").collectList().block(); assertEquals(1, airports1.size()); @@ -121,6 +125,37 @@ public void testCas() { 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 = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0); + System.out.println("------------------------------"); + System.out.println(airport.block()); + System.out.println("------------------------------"); + Flux airports = airportRepository.findPolicySnapshotAll(); + System.out.println(airports.collectList().block()); + System.out.println("------------------------------"); + Mono ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now()); + System.out.println(ap.block()); + } finally { + airportRepository.delete(saved1).block(); + airportRepository.delete(saved2).block(); + } + } + + public Mono 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 iatas = new HashSet(); @@ -195,6 +230,25 @@ void deleteAllById() { } } + @Test + void deleteAll() { + + Airport vienna = new Airport("airports::vie", "vie", "LOWW"); + Airport frankfurt = new Airport("airports::fra", "fra", "EDDF"); + Airport losAngeles = new Airport("airports::lax", "lax", "KLAX"); + + try { + airportRepository.saveAll(asList(vienna, frankfurt, losAngeles)).as(StepVerifier::create) + .expectNext(vienna, frankfurt, losAngeles).verifyComplete(); + + airportRepository.deleteAll().as(StepVerifier::create).verifyComplete(); + + airportRepository.findAll().as(StepVerifier::create).verifyComplete(); + } finally { + airportRepository.deleteAll().block(); + } + } + @Configuration @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") static class Config extends AbstractCouchbaseConfiguration { diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java index 65f99fa93..a7f12ccd0 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java @@ -32,6 +32,8 @@ 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 @@ void createsQueryCorrectly() throws Exception { 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 @@ void queryParametersArray() throws Exception { // 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 @@ void queryParametersJsonArray() throws Exception { 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 @@ void queryParametersList() throws Exception { 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")); } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java index 06968b4fc..bf6110988 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java @@ -30,7 +30,7 @@ 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 @@ queryMethod, converter, config().bucketname(), new SpelExpressionParser(), try { Thread.sleep(3000); } catch (Exception e) {} - ExecutableFindByQueryOperation.ExecutableFindByQuery q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) couchbaseTemplate + ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate .findByQuery(Airline.class).matching(query); Optional al = q.one(); diff --git a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java index 621453deb..1301d5d03 100644 --- a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java @@ -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 void beforeEach() {} 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); } } diff --git a/src/test/java/org/springframework/data/couchbase/util/CollectionAwareIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/CollectionAwareIntegrationTests.java index c0e8258c5..fb54b55dc 100644 --- a/src/test/java/org/springframework/data/couchbase/util/CollectionAwareIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/CollectionAwareIntegrationTests.java @@ -19,6 +19,8 @@ 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.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 @@ */ 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 static void beforeAll() { 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() {}); } } diff --git a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java index d0ac74d2e..5ba592487 100644 --- a/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/util/JavaIntegrationTests.java @@ -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.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.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 @@ @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 static void setupScopeCollection(Cluster cluster, String scopeName, Strin 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 static CompletableFuture createPrimaryIndex(Cluster cluster, String 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 static void createFtsCollectionIndex(Cluster cluster, String indexName, S 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"))) { diff --git a/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java b/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java index cb3f6de50..8ec65570b 100644 --- a/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java +++ b/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java @@ -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; @@ -32,7 +32,6 @@ import java.util.Map; import java.util.Optional; import java.util.Properties; -import java.util.UUID; public class UnmanagedTestCluster extends TestCluster { @@ -43,6 +42,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]; @@ -59,7 +59,7 @@ ClusterType type() { @Override TestClusterConfig _start() throws Exception { - bucketname = UUID.randomUUID().toString(); + bucketname = "my_bucket"; // UUID.randomUUID().toString(); Response postResponse = httpClient .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) @@ -69,8 +69,9 @@ TestClusterConfig _start() throws Exception { .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 +141,13 @@ private void waitUntilAllNodesHealthy() throws Exception { @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); } diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index b45efd19d..57bffc18d 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -21,9 +21,11 @@ - log details of the detection of placeholders in N1QL inline queries - log additional debug info during automatic index creation --> + + " - \ No newline at end of file +