From 957744b231273cfb6cb4cb69a5b82fab4bc06190 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Thu, 23 Jul 2020 17:11:37 -0700 Subject: [PATCH 1/3] DATACOUCH-588 - Implement pageable and realign repo query This implements pageable for non-reactive queries and realigns reactive queries with other spring-data projects to facilitate the implementaion of pageable (done) and other types of queries such as projection and distinct (not yet done) --- ...ExecutableFindByQueryOperationSupport.java | 3 +- .../core/ReactiveFindByQueryOperation.java | 125 ++++++- .../ReactiveFindByQueryOperationSupport.java | 9 + .../data/couchbase/core/query/Query.java | 11 +- .../query/AbstractCouchbaseQuery.java | 251 ++++++++++++++ .../query/AbstractReactiveCouchbaseQuery.java | 269 +++++++++++++++ .../repository/query/BooleanUtil.java | 49 +++ .../query/CouchbaseQueryExecution.java | 305 ++++++++++++++++++ .../query/CouchbaseQueryMethod.java | 50 +++ .../query/CouchbaseRepositoryQuery.java | 11 +- .../query/DtoInstantiatingConverter.java | 110 +++++++ .../query/N1qlRepositoryQueryExecutor.java | 34 +- .../query/PartTreeCouchbaseQuery.java | 138 ++++++++ .../ReactiveCouchbaseQueryExecution.java | 170 ++++++++++ .../query/ReactiveCouchbaseQueryMethod.java | 135 ++++++++ .../ReactiveCouchbaseRepositoryQuery.java | 36 ++- .../ReactiveN1qlRepositoryQueryExecutor.java | 44 +-- .../query/ReactivePartTreeCouchbaseQuery.java | 107 ++++++ .../query/ReactivePartTreeN1qlBasedQuery.java | 2 + .../ReactiveStringBasedCouchbaseQuery.java | 118 +++++++ .../query/StringBasedCouchbaseQuery.java | 139 ++++++++ .../query/StringN1qlQueryCreator.java | 27 +- .../support/CouchbaseRepositoryFactory.java | 38 +-- .../ReactiveCouchbaseRepositoryFactory.java | 33 +- .../couchbase/domain/AirportRepository.java | 9 +- .../domain/ReactiveAirportRepository.java | 24 ++ ...chbaseRepositoryQueryIntegrationTests.java | 20 +- ...chbaseRepositoryQueryIntegrationTests.java | 57 +++- .../StringN1qlQueryCreatorMockedTests.java | 9 +- .../query/StringN1qlQueryCreatorTests.java | 3 +- 30 files changed, 2211 insertions(+), 125 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java 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 7f569501b..f0ae9d679 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -51,7 +51,8 @@ static class ExecutableFindByQuerySupport implements ExecutableFindByQuery this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), domainType, query, scanConsistency); + this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), + domainType, query, scanConsistency); this.scanConsistency = scanConsistency; } 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 309431f9c..a414b1cbc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -104,6 +104,129 @@ interface FindByQueryConsistentWith extends FindByQueryWithQuery { } - interface ReactiveFindByQuery extends FindByQueryConsistentWith {} + /** + * Collection override (optional). + */ + interface FindWithCollection extends FindByQueryWithQuery { + + /** + * 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 FindWithProjection}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + FindWithProjection inCollection(String collection); + } + + /** + * Result type override (optional). + */ + interface FindWithProjection extends FindByQueryWithQuery, FindDistinct { + + /** + * Define the target type fields should be mapped to.
+ * Skip this step if you are anyway only interested in the original domain type. + * + * @param resultType must not be {@literal null}. + * @param result type. + * @return new instance of {@link FindWithProjection}. + * @throws IllegalArgumentException if resultType is {@literal null}. + */ + FindByQueryWithQuery as(Class resultType); + } + + /** + * Distinct Find support. + * + * @author Christoph Strobl + * @since 2.1 + */ + interface FindDistinct { + + /** + * Finds the distinct values for a specified {@literal field} across a single {@link } or view. + * + * @param field name of the field. Must not be {@literal null}. + * @return new instance of {@link TerminatingDistinct}. + * @throws IllegalArgumentException if field is {@literal null}. + */ + TerminatingDistinct distinct(String field); + } + + /** + * Result type override. Optional. + * + * @author Christoph Strobl + * @since 2.1 + */ + interface DistinctWithProjection { + + /** + * Define the target type the result should be mapped to.
+ * Skip this step if you are anyway fine with the default conversion. + *
+ *
{@link Object} (the default)
+ *
Result is mapped according to the {@link } converting eg. {@link } into plain {@link String}, {@link } to + * {@link Long}, etc. always picking the most concrete type with respect to the domain types property.
+ * Any {@link } is run through the {@link org.springframework.data.convert.EntityReader} to obtain the domain type. + *
+ * Using {@link Object} also works for non strictly typed fields. Eg. a mixture different types like fields using + * {@link String} in one {@link } while {@link Long} in another.
+ *
Any Simple type like {@link String}, {@link Long}, ...
+ *
The result is mapped directly by the Couchbase Java driver and the {@link } in place. This works only for + * results where all documents considered for the operation use the very same type for the field.
+ *
Any Domain type
+ *
Domain types can only be mapped if the if the result of the actual {@code distinct()} operation returns + * {@link }.
+ *
{@link }
+ *
Using {@link } allows retrieval of the raw driver specific format, which returns eg. {@link }.
+ *
+ * + * @param resultType must not be {@literal null}. + * @param result type. + * @return new instance of {@link TerminatingDistinct}. + * @throws IllegalArgumentException if resultType is {@literal null}. + */ + TerminatingDistinct as(Class resultType); + } + + /** + * Result restrictions. Optional. + * + * @author Christoph Strobl + * @since 2.1 + */ + interface DistinctWithQuery extends DistinctWithProjection { + + /** + * Set the filter {@link Query criteria} to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingDistinct}. + * @throws IllegalArgumentException if criteria is {@literal null}. + * @since 3.0 + */ + TerminatingDistinct matching(Query query); + } + + /** + * Terminating distinct find operations. + * + * @author Christoph Strobl + * @since 2.1 + */ + interface TerminatingDistinct extends DistinctWithQuery { + + /** + * Get all matching distinct field values. + * + * @return empty {@link Flux} if not match found. Never {@literal null}. + */ + Flux all(); + } + + interface ReactiveFindByQuery extends FindByQueryConsistentWith, FindWithCollection, FindDistinct {} } 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 a538be87d..53ff56562 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -130,6 +130,15 @@ private String assembleEntityQuery(final boolean count) { return query.toN1qlSelectString(template, this.domainType, count); } + @Override + public FindWithProjection inCollection(String collection) { + throw new RuntimeException(("not implemented")); + } + + @Override + public TerminatingDistinct distinct(String field) { + throw new RuntimeException(("not implemented")); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index 4ad556e85..d19788586 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -106,13 +106,22 @@ public Query skip(long skip) { * Limit the number of returned documents to {@code limit}. * * @param limit - * @return + * @return this */ public Query limit(int limit) { this.limit = limit; return this; } + /** + * limit + * + * @return limit + */ + public int getLimit() { + return limit; + } + /** * Sets the given pagination information on the {@link Query} instance. Will transparently set {@code skip} and * {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}. 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 new file mode 100644 index 000000000..6482866b3 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQuery.java @@ -0,0 +1,251 @@ +package org.springframework.data.couchbase.repository.query; + +import org.reactivestreams.Publisher; +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.query.Query; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.repository.core.EntityMetadata; +import org.springframework.data.repository.query.*; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public abstract class AbstractCouchbaseQuery implements RepositoryQuery { + + private final CouchbaseQueryMethod method; + private final CouchbaseOperations operations; + private final EntityInstantiators instantiators; + // private final FindWithProjection findOperationWithProjection; + private final ExecutableFindByQueryOperation.ExecutableFindByQuery findOperationWithProjection; + private final SpelExpressionParser expressionParser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@link AbstractCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and + * {@link org.springframework.data.couchbase.core.CouchbaseOperations}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + */ + public AbstractCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + + Assert.notNull(method, "CouchbaseQueryMethod must not be null!"); + Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!"); + Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); + Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!"); + + this.method = method; + this.operations = operations; + this.instantiators = new EntityInstantiators(); + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; + + EntityMetadata metadata = method.getEntityInformation(); + Class type = metadata.getJavaType(); + + this.findOperationWithProjection = operations.findByQuery(type); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() + */ + public CouchbaseQueryMethod getQueryMethod() { + return method; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) + */ + public Object execute(Object[] parameters) { + + return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) + : execute(new ReactiveCouchbaseParameterAccessor(method, parameters)); + } + + @SuppressWarnings("unchecked") + private Object executeDeferred(Object[] parameters) { + + ReactiveCouchbaseParameterAccessor parameterAccessor = new ReactiveCouchbaseParameterAccessor(method, parameters); + + if (getQueryMethod().isCollectionQuery()) { + return Flux.defer(() -> (Publisher) execute(parameterAccessor)); + } + + return Mono.defer(() -> (Mono) execute(parameterAccessor)); + } + + private Object execute(ParametersParameterAccessor parameterAccessor) { + + // ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(), + // parameterAccessor); + + TypeInformation returnType = ClassTypeInformation + .from(method.getResultProcessor().getReturnedType().getReturnedType()); + ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); + Class typeToRead = processor.getReturnedType().getTypeToRead(); + + if (typeToRead == null && returnType.getComponentType() != null) { + typeToRead = returnType.getComponentType().getType(); + } + + return doExecute(method, processor, parameterAccessor, typeToRead); + } + + /** + * Execute the {@link RepositoryQuery} of the given method with the parameters provided by the + * {@link ParametersParameterAccessor accessor} + * + * @param method the {@link ReactiveCouchbaseQueryMethod} invoked. Never {@literal null}. + * @param processor {@link ResultProcessor} for post procession. Never {@literal null}. + * @param accessor for providing invocation arguments. Never {@literal null}. + * @param typeToRead the desired component target type. Can be {@literal null}. + */ + protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processor, + ParametersParameterAccessor accessor, @Nullable Class typeToRead) { + + Query query = createQuery(accessor); + + query = applyAnnotatedConsistencyIfPresent(query); + // query = applyAnnotatedCollationIfPresent(query, accessor); + + ExecutableFindByQueryOperation.ExecutableFindByQuery find = typeToRead == null // + ? findOperationWithProjection // + : findOperationWithProjection; // TODO .as(typeToRead); + + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); + + CouchbaseQueryExecution execution = getExecution(accessor, + new CouchbaseQueryExecution.ResultProcessingConverter(processor, getOperations(), instantiators), find); + return execution.execute(query, processor.getReturnedType().getDomainType(), collection); + } + + /** + * Returns the execution instance to use. + * + * @param accessor must not be {@literal null}. + * @param resultProcessing must not be {@literal null}. + * @return + */ + private CouchbaseQueryExecution getExecution(ParameterAccessor accessor, Converter resultProcessing, + ExecutableFindByQueryOperation.ExecutableFindByQuery operation) { + return new CouchbaseQueryExecution.ResultProcessingExecution(getExecutionToWrap(accessor, operation), + resultProcessing); + } + + private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, + ExecutableFindByQueryOperation.ExecutableFindByQuery operation) { + + if (isDeleteQuery()) { + return new CouchbaseQueryExecution.DeleteExecution(getOperations(), method); + // } else if (isTailable(method)) { + // return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); + } else if (method.isCollectionQuery()) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); + } else if (isCountQuery()) { + return (q, t, c) -> operation.matching(q).count(); + } else if (isExistsQuery()) { + return (q, t, c) -> operation.matching(q).exists(); + } else if (method.isPageQuery()) { + return new CouchbaseQueryExecution.PagedExecution(operation, accessor.getPageable()); + } else { + return (q, t, c) -> { + + ExecutableFindByQueryOperation.TerminatingFindByQuery find = operation.matching(q); + + if (isCountQuery()) { + return find.count(); + } + + return isLimiting() ? find.first() : find.one(); + }; + } + } + + private boolean isTailable(ReactiveCouchbaseQueryMethod method) { + return false; // method.getTailableAnnotation() != null; + } + + /** + * Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given + * {@link Query} if present. + * + * @param query the {@link Query} to potentially apply the sort to. + * @return the query with potential scan consistency applied. + * @since 4.1 + */ + Query applyAnnotatedConsistencyIfPresent(Query query) { + + if (!method.hasScanConsistencyAnnotation()) { + return query; + } + return query.scanConsistency(method.getScanConsistencyAnnotation().query()); + } + + /** + * Creates a {@link Query} instance using the given {@link ParametersParameterAccessor}. Will delegate to + * {@link #createQuery(ParametersParameterAccessor)} by default but allows customization of the count query to be + * triggered. + * + * @param accessor must not be {@literal null}. + * @return + */ + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); + } + + /** + * Creates a {@link Query} instance using the given {@link ParameterAccessor} + * + * @param accessor must not be {@literal null}. + * @return + */ + protected abstract Query createQuery(ParametersParameterAccessor accessor); + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isCountQuery() + */ + protected boolean isCountQuery() { + return getQueryMethod().isCountQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isExistsQuery() + */ + + protected boolean isExistsQuery() { + return getQueryMethod().isExistsQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isDeleteQuery() + */ + protected boolean isDeleteQuery() { + return getQueryMethod().isDeleteQuery(); + } + + /** + * Return whether the query has an explicit limit set. + * + * @return + * @since 2.0.4 + */ + protected abstract boolean isLimiting(); + + public CouchbaseOperations getOperations() { + return operations; + } +} 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 new file mode 100644 index 000000000..2b90d6557 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractReactiveCouchbaseQuery.java @@ -0,0 +1,269 @@ +/* + * Copyright 2016-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.repository.query; + +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; +import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation.ReactiveFindByQuery; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.core.EntityMetadata; +import org.springframework.data.repository.query.*; +import org.springframework.data.util.ClassTypeInformation; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.reactivestreams.Publisher; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.mapping.model.EntityInstantiators; + +import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.DeleteExecution; +import org.springframework.data.util.TypeInformation; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Base class for reactive {@link RepositoryQuery} implementations for Couchbase. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.0 + */ +public abstract class AbstractReactiveCouchbaseQuery implements RepositoryQuery { + + private final ReactiveCouchbaseQueryMethod method; + private final ReactiveCouchbaseOperations operations; + private final EntityInstantiators instantiators; + // private final FindWithProjection findOperationWithProjection; + private final ReactiveFindByQuery findOperationWithProjection; + private final SpelExpressionParser expressionParser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@link AbstractReactiveCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and + * {@link org.springframework.data.couchbase.core.CouchbaseOperations}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + */ + public AbstractReactiveCouchbaseQuery(ReactiveCouchbaseQueryMethod method, ReactiveCouchbaseOperations operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + + Assert.notNull(method, "CouchbaseQueryMethod must not be null!"); + Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!"); + Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); + Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!"); + + this.method = method; + this.operations = operations; + this.instantiators = new EntityInstantiators(); + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; + + EntityMetadata metadata = method.getEntityInformation(); + Class type = metadata.getJavaType(); + + this.findOperationWithProjection = operations.findByQuery(type); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() + */ + public ReactiveCouchbaseQueryMethod getQueryMethod() { + return method; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) + */ + public Object execute(Object[] parameters) { + + return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) + : execute(new ReactiveCouchbaseParameterAccessor(method, parameters)); + } + + @SuppressWarnings("unchecked") + private Object executeDeferred(Object[] parameters) { + + ReactiveCouchbaseParameterAccessor parameterAccessor = new ReactiveCouchbaseParameterAccessor(method, parameters); + + if (getQueryMethod().isCollectionQuery()) { + return Flux.defer(() -> (Publisher) execute(parameterAccessor)); + } + + return Mono.defer(() -> (Mono) execute(parameterAccessor)); + } + + private Object execute(ReactiveCouchbaseParameterAccessor parameterAccessor) { + + // ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(), + // parameterAccessor); + + TypeInformation returnType = ClassTypeInformation + .from(method.getResultProcessor().getReturnedType().getReturnedType()); + ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); + Class typeToRead = processor.getReturnedType().getTypeToRead(); + + if (typeToRead == null && returnType.getComponentType() != null) { + typeToRead = returnType.getComponentType().getType(); + } + + return doExecute(method, processor, parameterAccessor, typeToRead); + } + + /** + * Execute the {@link RepositoryQuery} of the given method with the parameters provided by the + * {@link ParametersParameterAccessor accessor} + * + * @param method the {@link ReactiveCouchbaseQueryMethod} invoked. Never {@literal null}. + * @param processor {@link ResultProcessor} for post procession. Never {@literal null}. + * @param accessor for providing invocation arguments. Never {@literal null}. + * @param typeToRead the desired component target type. Can be {@literal null}. + */ + protected Object doExecute(ReactiveCouchbaseQueryMethod method, ResultProcessor processor, + ParametersParameterAccessor accessor, @Nullable Class typeToRead) { + + Query query = createQuery(accessor); + + query = applyAnnotatedConsistencyIfPresent(query); + // query = applyAnnotatedCollationIfPresent(query, accessor); + + ReactiveFindByQueryOperation.FindByQueryWithQuery find = typeToRead == null // + ? findOperationWithProjection // + : findOperationWithProjection; // TODO .as(typeToRead); + + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); + + ReactiveCouchbaseQueryExecution execution = getExecution(accessor, + new ReactiveCouchbaseQueryExecution.ResultProcessingConverter(processor, getOperations(), instantiators), find); + return execution.execute(query, processor.getReturnedType().getDomainType(), collection); + } + + /** + * Returns the execution instance to use. + * + * @param accessor must not be {@literal null}. + * @param resultProcessing must not be {@literal null}. + * @return + */ + private ReactiveCouchbaseQueryExecution getExecution(ParameterAccessor accessor, + Converter resultProcessing, ReactiveFindByQueryOperation.FindByQueryWithQuery operation) { + return new ReactiveCouchbaseQueryExecution.ResultProcessingExecution(getExecutionToWrap(accessor, operation), + resultProcessing); + } + + private ReactiveCouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, + ReactiveFindByQueryOperation.FindByQueryWithQuery operation) { + + if (isDeleteQuery()) { + return new DeleteExecution(getOperations(), method); + // } else if (isTailable(method)) { + // return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); + } else if (method.isCollectionQuery()) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); + } else if (isCountQuery()) { + return (q, t, c) -> operation.matching(q).count(); + } else if (isExistsQuery()) { + return (q, t, c) -> operation.matching(q).exists(); + } else { + return (q, t, c) -> { + ReactiveFindByQueryOperation.TerminatingFindByQuery find = operation.matching(q); + return isLimiting() ? find.first() : find.one(); + }; + } + } + + private boolean isTailable(ReactiveCouchbaseQueryMethod method) { + return false; // method.getTailableAnnotation() != null; + } + + /** + * Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given + * {@link Query} if present. + * + * @param query the {@link Query} to potentially apply the sort to. + * @return the query with potential scan consistency applied. + * @since 4.1 + */ + Query applyAnnotatedConsistencyIfPresent(Query query) { + + if (!method.hasScanConsistencyAnnotation()) { + return query; + } + return query.scanConsistency(method.getScanConsistencyAnnotation().query()); + } + + /** + * Creates a {@link Query} instance using the given {@link ParametersParameterAccessor}. Will delegate to + * {@link #createQuery(ParametersParameterAccessor)} by default but allows customization of the count query to be + * triggered. + * + * @param accessor must not be {@literal null}. + * @return + */ + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); + } + + /** + * Creates a {@link Query} instance using the given {@link ParameterAccessor} + * + * @param accessor must not be {@literal null}. + * @return + */ + protected abstract Query createQuery(ParametersParameterAccessor accessor); + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isCountQuery() + */ + protected boolean isCountQuery() { + return getQueryMethod().isCountQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isExistsQuery() + */ + + protected boolean isExistsQuery() { + return getQueryMethod().isExistsQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isDeleteQuery() + */ + protected boolean isDeleteQuery() { + return getQueryMethod().isDeleteQuery(); + } + + /** + * Return whether the query has an explicit limit set. + * + * @return + * @since 2.0.4 + */ + protected abstract boolean isLimiting(); + + public ReactiveCouchbaseOperations getOperations() { + return operations; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java b/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java new file mode 100644 index 000000000..b7113e07b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018-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.repository.query; + +/** + * Utility class containing methods to interact with boolean values. + * + * @author Mark Paluch + * @since 2.0.9 + */ +final class BooleanUtil { + + private BooleanUtil() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + /** + * Count the number of {@literal true} values. + * + * @param values + * @return the number of values that are {@literal true}. + */ + static int countBooleanTrueValues(boolean... values) { + + int count = 0; + + for (boolean value : values) { + + if (value) { + count++; + } + } + + return count; + } +} 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 new file mode 100644 index 000000000..79f1c93ed --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryExecution.java @@ -0,0 +1,305 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.repository.query; + +import java.util.List; + +import org.reactivestreams.Publisher; +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.query.Query; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; +import org.springframework.data.repository.support.PageableExecutionUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a + * {@link org.springframework.data.repository.query.QueryMethod} a {@link Query} can be executed in various flavors. + * TODO: seems to be a lot of duplication with ReactiveCouchbaseQueryExecution + * + * @author Oliver Gierke + * @author Mark Paluch + * @author Christoph Strobl + * @author Michael Reiche + */ +@FunctionalInterface +interface CouchbaseQueryExecution { + + Object execute(Query query, Class type, String collection); + + /** + * {@link CouchbaseQueryExecution} removing documents matching the query. + * + * @author Mark Paluch + * @author Artyom Gabeev + */ + + final class DeleteExecution implements CouchbaseQueryExecution { + + private final CouchbaseOperations operations; + private final CouchbaseQueryMethod method; + + public DeleteExecution(CouchbaseOperations operations, CouchbaseQueryMethod method) { + this.operations = operations; + this.method = method; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery.Execution#execute(org.springframework.data.couchbase.core.query.Query, java.lang.Class, java.lang.String) + */ + + @Override + public Object execute(Query query, Class type, String collection) { + + // Class type = method.getEntityInformation().getJavaType(); + + // if (method.isCollectionQuery()) { + return operations.removeByQuery(type).matching(query).all(); + // } + + } + + } + + /** + * An {@link ReactiveCouchbaseQueryExecution} that wraps the results of the given delegate with the given result + * processing. + */ + final class ResultProcessingExecution implements CouchbaseQueryExecution { + + private final CouchbaseQueryExecution delegate; + private final Converter converter; + + public ResultProcessingExecution(CouchbaseQueryExecution delegate, Converter converter) { + + Assert.notNull(delegate, "Delegate must not be null!"); + Assert.notNull(converter, "Converter must not be null!"); + + this.delegate = delegate; + this.converter = converter; + } + + @Override + public Object execute(Query query, Class type, String collection) { + return converter.convert(delegate.execute(query, type, collection)); + } + } + + /** + * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. + * + * @author Mark Paluch + */ + final class ResultProcessingConverter implements Converter { + + private final ResultProcessor processor; + private final CouchbaseOperations operations; + private final EntityInstantiators instantiators; + + public ResultProcessingConverter(ResultProcessor processor, CouchbaseOperations operations, + EntityInstantiators instantiators) { + + Assert.notNull(processor, "Processor must not be null!"); + Assert.notNull(operations, "Operations must not be null!"); + Assert.notNull(instantiators, "Instantiators must not be null!"); + + this.processor = processor; + this.operations = operations; + this.instantiators = instantiators; + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Object convert(Object source) { + + ReturnedType returnedType = processor.getReturnedType(); + + if (isVoid(returnedType)) { + + if (source instanceof Mono) { + return ((Mono) source).then(); + } + + if (source instanceof Publisher) { + return Flux.from((Publisher) source).then(); + } + } + + if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { + return source; + } + + if (!operations.getConverter().getMappingContext().hasPersistentEntityFor(returnedType.getReturnedType())) { + return source; + } + + Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), + operations.getConverter().getMappingContext(), instantiators); + + return processor.processResult(source, converter); + } + } + + static boolean isVoid(ReturnedType returnedType) { + return returnedType.getReturnedType().equals(Void.class); + } + + /** + * {@link CouchbaseQueryExecution} for {@link Slice} query methods. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @since 1.5 + */ + final class SlicedExecution implements CouchbaseQueryExecution { + + private final ExecutableFindByQueryOperation.ExecutableFindByQuery find; + private final Pageable pageable; + + public SlicedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery find, Pageable pageable) { + + Assert.notNull(find, "Find must not be null!"); + Assert.notNull(pageable, "Pageable must not be null!"); + + this.find = find; + this.pageable = pageable; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution#execute(org.springframework.data.couchbase.core.query.Query) + */ + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Object execute(Query query, Class type, String collection) { + + int pageSize = pageable.getPageSize(); + + // Apply Pageable but tweak limit to peek into next page + Query modifiedQuery = query.with(pageable).limit(pageSize + 1); + List result = find.matching(modifiedQuery).all(); + + boolean hasNext = result.size() > pageSize; + + return new SliceImpl(hasNext ? result.subList(0, pageSize) : result, pageable, hasNext); + } + } + + /** + * {@link CouchbaseQueryExecution} for pagination queries. + * + * @author Oliver Gierke + * @author Mark Paluch + * @author Christoph Strobl + */ + final class PagedExecution implements CouchbaseQueryExecution { + + private final ExecutableFindByQueryOperation.ExecutableFindByQuery operation; + private final Pageable pageable; + + public PagedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery operation, Pageable pageable) { + + Assert.notNull(operation, "Operation must not be null!"); + Assert.notNull(pageable, "Pageable must not be null!"); + + this.operation = operation; + this.pageable = pageable; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution#execute(org.springframework.data.couchbase.core.query.Query) + */ + @Override + public Object execute(Query query, Class type, String collection) { + + int overallLimit = query.getLimit(); + + ExecutableFindByQueryOperation.TerminatingFindByQuery matching = operation.matching(query); + + // Apply raw pagination + query.with(pageable); + + // Adjust limit if page would exceed the overall limit + if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) { + query.limit((int) (overallLimit - pageable.getOffset())); + } + + return PageableExecutionUtils.getPage(matching.all(), pageable, () -> { + + long count = operation.matching(query.skip(-1).limit(-1)).count(); + return overallLimit != 0 ? Math.min(count, overallLimit) : count; + }); + } + } + + /* + final class ReactivePagedExecution implements CouchbaseQueryExecution { // TODO ?? + + private final ReactiveFindByQueryOperation.ReactiveFindByQuery operation; + private final Pageable pageable; + + public ReactivePagedExecution(ReactiveFindByQueryOperation.ReactiveFindByQuery operation, Pageable pageable) { + + Assert.notNull(operation, "Operation must not be null!"); + Assert.notNull(pageable, "Pageable must not be null!"); + + this.operation = operation; + this.pageable = pageable; + } + + + /xx* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution#execute(org.springframework.data.couchbase.core.query.Query) + *xx/ + @Override + public Object execute(Query query) { + + int overallLimit = query.getLimit(); + + ReactiveFindByQueryOperation.TerminatingFindByQuery matching = operation.matching(query); + + // Apply raw pagination + query.with(pageable); + + // Adjust limit if page would exceed the overall limit + if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) { + query.limit((int) (overallLimit - pageable.getOffset())); + } + + return PageableExecutionUtils.getPage(matching.all().collectList().block(), pageable, () -> { + + long count = operation.matching(query.skip(-1).limit(-1)).count().block(); + return overallLimit != 0 ? Math.min(count, overallLimit) : count; + }); + } + } + */ +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java index d230572ae..f53a5b6b3 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java @@ -29,7 +29,11 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.util.ReactiveWrapperConverters; +import org.springframework.data.repository.util.ReactiveWrappers; +import org.springframework.data.util.Lazy; import org.springframework.util.StringUtils; /** @@ -43,12 +47,30 @@ public class CouchbaseQueryMethod extends QueryMethod { private final Method method; + private final Lazy isCollectionQueryCouchbase; // not to be confused with QueryMethod.isCollectionQuery public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, MappingContext, CouchbasePersistentProperty> mappingContext) { super(method, metadata, factory); this.method = method; + this.isCollectionQueryCouchbase = Lazy.of(() -> { + boolean result = !(isPageQuery() || isSliceQuery()) + && ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()); + return result; + }); + } + + /* + * does this query return a collection? + * This must override QueryMethod.isCollection() as isCollectionQueryCouchbase is different from isCollectionQuery + * + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethod#isCollection() + */ + @Override + public boolean isCollectionQuery() { + return (Boolean) this.isCollectionQueryCouchbase.get(); } /** @@ -182,6 +204,24 @@ public String getInlineN1qlQuery() { return StringUtils.hasText(query) ? query : null; } + /** + * is this a 'delete'? + * + * @return is this a 'delete'? + */ + public boolean isDeleteQuery() { + return getName().toLowerCase().startsWith("delete"); + } + + /** + * is this an 'exists' query? + * + * @return is this an 'exists' query? + */ + public boolean isExistsQuery() { + return getName().toLowerCase().startsWith("exists"); + } + /** * indicates if the method begins with "count" * @@ -196,4 +236,14 @@ public boolean isCountQuery() { public String toString() { return super.toString(); } + + public boolean hasReactiveWrapperParameter() { + for (Parameter p : getParameters()) { + if (ReactiveWrapperConverters.supports(p.getType())) { + return true; + } + } + return false; + } + } 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 bef5d5161..c7fd87bd4 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 @@ -18,28 +18,35 @@ import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; /** * @author Michael Nitschinger * @author Michael Reiche + * @deprecated */ +@Deprecated public class CouchbaseRepositoryQuery implements RepositoryQuery { private final CouchbaseOperations operations; private final CouchbaseQueryMethod queryMethod; private final NamedQueries namedQueries; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; public CouchbaseRepositoryQuery(final CouchbaseOperations operations, final CouchbaseQueryMethod queryMethod, - final NamedQueries namedQueries) { + final NamedQueries namedQueries, final QueryMethodEvaluationContextProvider evaluationContextProvider) { this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; + this.evaluationContextProvider = evaluationContextProvider; + throw new RuntimeException("Deprecated"); } @Override public Object execute(final Object[] parameters) { - return new N1qlRepositoryQueryExecutor(operations, queryMethod, namedQueries).execute(parameters); + return new N1qlRepositoryQueryExecutor(operations, queryMethod, namedQueries, evaluationContextProvider) + .execute(parameters); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java b/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java new file mode 100644 index 000000000..62b3d4478 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-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.repository.query; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.SimplePropertyHandler; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.EntityInstantiator; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.util.Assert; + +/** + * {@link Converter} to instantiate DTOs from fully equipped domain objects. + * + * @author Oliver Gierke + * @author Mark Paluch + * @author Michael Reiche + */ +class DtoInstantiatingConverter implements Converter { + + private final Class targetType; + private final MappingContext, ? extends PersistentProperty> context; + private final EntityInstantiator instantiator; + + /** + * Creates a new {@link Converter} to instantiate DTOs. + * + * @param dtoType must not be {@literal null}. + * @param context must not be {@literal null}. + * @param entityInstantiators must not be {@literal null}. + */ + public DtoInstantiatingConverter(Class dtoType, + MappingContext, CouchbasePersistentProperty> context, + EntityInstantiators entityInstantiators) { + + Assert.notNull(dtoType, "DTO type must not be null!"); + Assert.notNull(context, "MappingContext must not be null!"); + Assert.notNull(entityInstantiators, "EntityInstantiators must not be null!"); + + this.targetType = dtoType; + this.context = context; + this.instantiator = entityInstantiators.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Object convert(Object source) { + + if (targetType.isInterface()) { + return source; + } + + final PersistentEntity sourceEntity = context.getRequiredPersistentEntity(source.getClass()); + final PersistentPropertyAccessor sourceAccessor = sourceEntity.getPropertyAccessor(source); + final PersistentEntity targetEntity = context.getRequiredPersistentEntity(targetType); + final PreferredConstructor> constructor = targetEntity + .getPersistenceConstructor(); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + Object dto = instantiator.createInstance(targetEntity, new ParameterValueProvider() { + + @Override + public Object getParameterValue(Parameter parameter) { + return sourceAccessor.getProperty(sourceEntity.getPersistentProperty(parameter.getName().toString())); + } + }); + + final PersistentPropertyAccessor dtoAccessor = targetEntity.getPropertyAccessor(dto); + + targetEntity.doWithProperties(new SimplePropertyHandler() { + + @Override + public void doWithPersistentProperty(PersistentProperty property) { + + if (constructor.isConstructorParameter(property)) { + return; + } + + dtoAccessor.setProperty(property, + sourceAccessor.getProperty(sourceEntity.getPersistentProperty(property.getName()))); + } + }); + + return dto; + } +} 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 9f19847cd..e2d0d8963 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 @@ -19,29 +19,39 @@ import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** * @author Michael Nitschinger * @author Michael Reiche + * @deprecated */ +@Deprecated public class N1qlRepositoryQueryExecutor { private final CouchbaseOperations operations; private final CouchbaseQueryMethod queryMethod; private final NamedQueries namedQueries; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; public N1qlRepositoryQueryExecutor(final CouchbaseOperations operations, final CouchbaseQueryMethod queryMethod, - final NamedQueries namedQueries) { + final NamedQueries namedQueries, final QueryMethodEvaluationContextProvider evaluationContextProvider) { this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; + this.evaluationContextProvider = evaluationContextProvider; } + private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); + /** * see also {@link ReactiveN1qlRepositoryQueryExecutor#execute(Object[] parameters) execute } * @@ -52,25 +62,31 @@ public Object execute(final Object[] parameters) { final Class domainClass = queryMethod.getResultProcessor().getReturnedType().getDomainType(); final ParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); - // this is identical to ReactiveN1qlRespositoryQueryExecutor, - // except for the type of 'q', and the call to oneValue() vs one() + // counterpart to ReactiveN1qlRespositoryQueryExecutor, Query query; ExecutableFindByQueryOperation.ExecutableFindByQuery q; if (queryMethod.hasN1qlAnnotation()) { query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(), - QueryMethodEvaluationContextProvider.DEFAULT, namedQueries).createQuery(); + SPEL_PARSER, evaluationContextProvider, namedQueries).createQuery(); } else { final PartTree tree = new PartTree(queryMethod.getName(), domainClass); query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery(); } - q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass) - .consistentWith(buildQueryScanConsistency()).matching(query); + + //q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass) + // .consistentWith(buildQueryScanConsistency()).matching(query); + + ExecutableFindByQueryOperation.ExecutableFindByQuery operation = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations + .findByQuery(domainClass).consistentWith(buildQueryScanConsistency()); if (queryMethod.isCountQuery()) { - return q.count(); + return operation.matching(query).count(); } else if (queryMethod.isCollectionQuery()) { - return q.all(); + return operation.matching(query).all(); + } else if (queryMethod.isPageQuery()) { + Pageable p = accessor.getPageable(); + return new CouchbaseQueryExecution.PagedExecution(operation, p).execute(query, null, null); } else { - return q.oneValue(); + return operation.matching(query).oneValue(); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java new file mode 100644 index 000000000..13c0436ce --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-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.repository.query; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * {@link RepositoryQuery} implementation for Couchbase. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @author Thomas Darimont + * @author Mark Paluch + */ +public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery { + + private final PartTree tree; + // private final boolean isGeoNearQuery; + private final MappingContext context; + private final ResultProcessor processor; + private final CouchbaseConverter converter; + + /** + * Creates a new {@link PartTreeCouchbaseQuery} from the given {@link QueryMethod} and {@link CouchbaseTemplate}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + */ + public PartTreeCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + + super(method, operations, expressionParser, evaluationContextProvider); + + this.processor = method.getResultProcessor(); + // TODO - why the for loop? Why not just : + // this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()) + for (PartTree.OrPart parts : this.tree = new PartTree(method.getName(), + processor.getReturnedType().getDomainType())) { + } + + this.context = operations.getConverter().getMappingContext(); + this.converter = operations.getConverter(); + } + + /** + * Return the {@link PartTree} backing the query. + * + * @return the tree + */ + public PartTree getTree() { + return tree; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#createQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor, boolean) + */ + @Override + protected Query createQuery(ParametersParameterAccessor accessor) { + + N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter); + Query query = creator.createQuery(); + + if (tree.isLimiting()) { + query.limit(tree.getMaxResults()); + } + return query; + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#createCountQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor) + */ + @Override + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter).createQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isCountQuery() + */ + @Override + protected boolean isCountQuery() { + return tree.isCountProjection(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isDeleteQuery() + */ + @Override + protected boolean isDeleteQuery() { + return tree.isDelete(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isLimiting() + */ + @Override + protected boolean isLimiting() { + return tree.isLimiting(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java new file mode 100644 index 000000000..4106a0ffa --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java @@ -0,0 +1,170 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.repository.query; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.reactivestreams.Publisher; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a + * {@link org.springframework.data.repository.query.QueryMethod} a {@link AbstractReactiveCouchbaseQuery} can be + * executed in various flavors. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.0 + */ +interface ReactiveCouchbaseQueryExecution { + + Object execute(Query query, Class type, String collection); + + /** + * {@link ReactiveCouchbaseQueryExecution} removing documents matching the query. + * + * @author Mark Paluch + * @author Artyom Gabeev + */ + + final class DeleteExecution implements ReactiveCouchbaseQueryExecution { + + private final ReactiveCouchbaseOperations operations; + private final CouchbaseQueryMethod method; + + public DeleteExecution(ReactiveCouchbaseOperations operations, CouchbaseQueryMethod method) { + this.operations = operations; + this.method = method; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery.Execution#execute(org.springframework.data.couchbase.core.query.Query, java.lang.Class, java.lang.String) + */ + + @Override + public Object execute(Query query, Class type, String collection) { + + // if (method.isCollectionQuery()) { + return operations.removeByQuery(type).matching(query).all(); + // } + + /* + if (method.isQueryForEntity() && !ClassUtils.isPrimitiveOrWrapper(method.getReturnedObjectType())) { + return operations.removeByQuery(query, type); + } + + return operations.removeByQuery(query, type, collection) + .map(deleteResult -> deleteResult.wasAcknowledged() ? deleteResult.getDeletedCount() : 0L); + */ + } + + } + + /** + * An {@link ReactiveCouchbaseQueryExecution} that wraps the results of the given delegate with the given result + * processing. + */ + final class ResultProcessingExecution implements ReactiveCouchbaseQueryExecution { + + private final ReactiveCouchbaseQueryExecution delegate; + private final Converter converter; + + public ResultProcessingExecution(ReactiveCouchbaseQueryExecution delegate, Converter converter) { + + Assert.notNull(delegate, "Delegate must not be null!"); + Assert.notNull(converter, "Converter must not be null!"); + + this.delegate = delegate; + this.converter = converter; + } + + @Override + public Object execute(Query query, Class type, String collection) { + return converter.convert(delegate.execute(query, type, collection)); + } + } + + /** + * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. + * + * @author Mark Paluch + */ + final class ResultProcessingConverter implements Converter { + + private final ResultProcessor processor; + private final ReactiveCouchbaseOperations operations; + private final EntityInstantiators instantiators; + + public ResultProcessingConverter(ResultProcessor processor, ReactiveCouchbaseOperations operations, + EntityInstantiators instantiators) { + + Assert.notNull(processor, "Processor must not be null!"); + Assert.notNull(operations, "Operations must not be null!"); + Assert.notNull(instantiators, "Instantiators must not be null!"); + + this.processor = processor; + this.operations = operations; + this.instantiators = instantiators; + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Object convert(Object source) { + + ReturnedType returnedType = processor.getReturnedType(); + + if (isVoid(returnedType)) { + + if (source instanceof Mono) { + return ((Mono) source).then(); + } + + if (source instanceof Publisher) { + return Flux.from((Publisher) source).then(); + } + } + + if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { + return source; + } + + if (!operations.getConverter().getMappingContext().hasPersistentEntityFor(returnedType.getReturnedType())) { + return source; + } + + Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), + operations.getConverter().getMappingContext(), instantiators); + + return processor.processResult(source, converter); + } + } + + static boolean isVoid(ReturnedType returnedType) { + return returnedType.getReturnedType().equals(Void.class); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java new file mode 100644 index 000000000..13892db41 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016-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.repository.query; + +import static org.springframework.data.repository.util.ClassUtils.*; + +import java.lang.reflect.Method; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.util.ReactiveWrappers; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Lazy; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; + +/** + * Reactive specific implementation of {@link CouchbaseQueryMethod}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.0 + */ +public class ReactiveCouchbaseQueryMethod extends CouchbaseQueryMethod { + + private static final ClassTypeInformation PAGE_TYPE = ClassTypeInformation.from(Page.class); + private static final ClassTypeInformation SLICE_TYPE = ClassTypeInformation.from(Slice.class); + + private final Method method; + //private final Lazy isCollectionQuery; + + /** + * Creates a new {@link ReactiveCouchbaseQueryMethod} from the given {@link Method}. + * + * @param method must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param projectionFactory must not be {@literal null}. + * @param mappingContext must not be {@literal null}. + */ + public ReactiveCouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory projectionFactory, + MappingContext, CouchbasePersistentProperty> mappingContext) { + + super(method, metadata, projectionFactory, mappingContext); + + if (hasParameterOfType(method, Pageable.class)) { + + TypeInformation returnType = ClassTypeInformation.fromReturnTypeOf(method); + + boolean multiWrapper = ReactiveWrappers.isMultiValueType(returnType.getType()); + boolean singleWrapperWithWrappedPageableResult = ReactiveWrappers.isSingleValueType(returnType.getType()) + && (PAGE_TYPE.isAssignableFrom(returnType.getRequiredComponentType()) + || SLICE_TYPE.isAssignableFrom(returnType.getRequiredComponentType())); + + if (singleWrapperWithWrappedPageableResult) { + throw new InvalidDataAccessApiUsageException( + String.format("'%s.%s' must not use sliced or paged execution. Please use Flux.buffer(size, skip).", + ClassUtils.getShortName(method.getDeclaringClass()), method.getName())); + } + + if (!multiWrapper) { + throw new IllegalStateException(String.format( + "Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: %s", + method.toString())); + } + + if (hasParameterOfType(method, Sort.class)) { + throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameter. " + + "Use sorting capabilities on Pageable instead! Offending method: %s", method.toString())); + } + } + + this.method = method; + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod#createParameters(java.lang.reflect.Method) + */ + /* + @Override + protected CouchbaseParameters createParameters(Method method) { + return new CouchbaseParameters(method, isGeoNearQuery(method)); + }*/ + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethod#isModifyingQuery() + */ + @Override + public boolean isModifyingQuery() { + return super.isModifyingQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethod#isQueryForEntity() + */ + @Override + public boolean isQueryForEntity() { + return super.isQueryForEntity(); + } + + /* + * All reactive query methods are streaming queries. + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethod#isStreamQuery() + */ + @Override + public boolean isStreamQuery() { + return true; + } + +} 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 128a89e62..d2b6b40c5 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 @@ -16,34 +16,56 @@ package org.springframework.data.couchbase.repository.query; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.repository.core.NamedQueries; -import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.spel.EvaluationContextProvider; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** * @author Michael Nitschinger * @author Michael Reiche + * @deprecated */ -public class ReactiveCouchbaseRepositoryQuery implements RepositoryQuery { +@Deprecated +public class ReactiveCouchbaseRepositoryQuery extends AbstractReactiveCouchbaseQuery { private final ReactiveCouchbaseOperations operations; - private final CouchbaseQueryMethod queryMethod; + private final ReactiveCouchbaseQueryMethod queryMethod; private final NamedQueries namedQueries; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; public ReactiveCouchbaseRepositoryQuery(final ReactiveCouchbaseOperations operations, - final CouchbaseQueryMethod queryMethod, final NamedQueries namedQueries) { + final ReactiveCouchbaseQueryMethod queryMethod, final NamedQueries namedQueries, + final QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(queryMethod, operations, new SpelExpressionParser(), evaluationContextProvider); this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; + this.evaluationContextProvider = evaluationContextProvider; + throw new RuntimeException("Deprecated"); } @Override public Object execute(final Object[] parameters) { - return new ReactiveN1qlRepositoryQueryExecutor(operations, queryMethod, namedQueries).execute(parameters); + return new ReactiveN1qlRepositoryQueryExecutor(operations, queryMethod, namedQueries, evaluationContextProvider) + .execute(parameters); } @Override - public QueryMethod getQueryMethod() { + protected Query createQuery(ParametersParameterAccessor accessor) { + return null; + } + + @Override + protected boolean isLimiting() { + // TODO + return false; + } + + @Override + public ReactiveCouchbaseQueryMethod getQueryMethod() { return queryMethod; } 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 bcc11eeb1..c15fa09df 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 @@ -18,6 +18,7 @@ import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; +import org.springframework.data.domain.Pageable; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.query.*; @@ -33,18 +34,23 @@ /** * @author Michael Nitschinger * @author Michael Reiche + * @deprecated */ +@Deprecated public class ReactiveN1qlRepositoryQueryExecutor { private final ReactiveCouchbaseOperations operations; - private final CouchbaseQueryMethod queryMethod; + private final ReactiveCouchbaseQueryMethod queryMethod; private final NamedQueries namedQueries; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; public ReactiveN1qlRepositoryQueryExecutor(final ReactiveCouchbaseOperations operations, - final CouchbaseQueryMethod queryMethod, final NamedQueries namedQueries) { + final ReactiveCouchbaseQueryMethod queryMethod, final NamedQueries namedQueries, + QueryMethodEvaluationContextProvider evaluationContextProvider) { this.operations = operations; this.queryMethod = queryMethod; this.namedQueries = namedQueries; + this.evaluationContextProvider = evaluationContextProvider; } /** @@ -54,41 +60,17 @@ public ReactiveN1qlRepositoryQueryExecutor(final ReactiveCouchbaseOperations ope * @return */ public Object execute(final Object[] parameters) { - final Class domainClass = queryMethod.getResultProcessor().getReturnedType().getDomainType(); - final ParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); - final String namedQueryName = queryMethod.getNamedQueryName(); - // this is identical to ExecutableN1qlRespositoryQueryExecutor, - // except for the type of 'q', and the call to one() vs oneValue() + // counterpart to N1qlRespositoryQueryExecutor, - Query query; - ReactiveFindByQueryOperation.ReactiveFindByQuery q; if (queryMethod.hasN1qlAnnotation()) { - query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(), - QueryMethodEvaluationContextProvider.DEFAULT, namedQueries).createQuery(); + return new ReactiveStringBasedCouchbaseQuery(queryMethod, operations, + new SpelExpressionParser(), evaluationContextProvider, namedQueries).execute(parameters); } else { - final PartTree tree = new PartTree(queryMethod.getName(), domainClass); - query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery(); + return new ReactivePartTreeCouchbaseQuery(queryMethod, operations, new SpelExpressionParser(), + evaluationContextProvider).execute(parameters); } - q = (ReactiveFindByQueryOperation.ReactiveFindByQuery) operations.findByQuery(domainClass) - .consistentWith(buildQueryScanConsistency()).matching(query); - if (queryMethod.isCountQuery()) { - return q.count(); - } else if (queryMethod.isCollectionQuery()) { - return q.all(); - } else { - return q.one(); - } - } - private QueryScanConsistency buildQueryScanConsistency() { - QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED; - if (queryMethod.hasConsistencyAnnotation()) { - scanConsistency = queryMethod.getConsistencyAnnotation().value(); - } else if (queryMethod.hasScanConsistencyAnnotation()) { - scanConsistency = queryMethod.getScanConsistencyAnnotation().query(); - } - return scanConsistency; } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java new file mode 100644 index 000000000..598d814d8 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-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.repository.query; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * Reactive PartTree {@link RepositoryQuery} implementation for Couchbase. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.0 + */ +public class ReactivePartTreeCouchbaseQuery extends AbstractReactiveCouchbaseQuery { + + private final PartTree tree; + private final CouchbaseConverter converter; + private static final Logger LOG = LoggerFactory.getLogger(ReactivePartTreeCouchbaseQuery.class); + + /** + * Creates a new {@link ReactivePartTreeCouchbaseQuery} from the given {@link QueryMethod} and + * {@link CouchbaseTemplate}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + */ + public ReactivePartTreeCouchbaseQuery(ReactiveCouchbaseQueryMethod method, ReactiveCouchbaseOperations operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + + super(method, operations, expressionParser, evaluationContextProvider); + this.tree = new PartTree(method.getName(), method.getResultProcessor().getReturnedType().getDomainType()); + this.converter = operations.getConverter(); + } + + /** + * Return the {@link PartTree} backing the query. + * + * @return the tree + */ + public PartTree getTree() { + return tree; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbease.repository.query.AbstractCouchbaseQuery#createQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor, boolean) + */ + @Override + protected Query createQuery(ParametersParameterAccessor accessor) { + + N1qlQueryCreator creator = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter); + Query query = creator.createQuery(); + + if (tree.isLimiting()) { + query.limit(tree.getMaxResults()); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Created query {} for * fields.", query.export()); + } + return query; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#createCountQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor) + */ + @Override + protected Query createCountQuery(ParametersParameterAccessor accessor) { + Query query = new N1qlQueryCreator(tree, accessor, getQueryMethod(), converter).createQuery(); + if (LOG.isDebugEnabled()) { + LOG.debug("Created query {} for * fields.", query.export()); + } + return query; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isLimiting() + */ + @Override + protected boolean isLimiting() { + return tree.isLimiting(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java index 3cfd991e8..b82e60908 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java @@ -32,7 +32,9 @@ * * @author Subhashni Balakrishnan * @since 3.0 + * @deprecated */ +@Deprecated public class ReactivePartTreeN1qlBasedQuery extends ReactiveAbstractN1qlBasedQuery { private final PartTree partTree; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java new file mode 100644 index 000000000..f39847c9e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016-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.repository.query; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; + +/** + * Query to use a plain JSON String to create the {@link Query} to actually execute. + * + * @author Mark Paluch + * @author Christoph Strobl + * @author Michael Reiche + * @since 2.0 + */ +public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbaseQuery { + + private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; + private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedCouchbaseQuery.class); + private final boolean isCountQuery; + private final boolean isExistsQuery; + private final boolean isDeleteQuery; + private final NamedQueries namedQueries; + + private final SpelExpressionParser expressionParser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@link ReactiveStringBasedCouchbaseQuery} for the given {@link String}, {@link CouchbaseQueryMethod}, + * {@link CouchbaseOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + * @param namedQueries + */ + public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method, ReactiveCouchbaseOperations operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider, + NamedQueries namedQueries) { + + super(method, operations, expressionParser, evaluationContextProvider); + + Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); + + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; + + this.isCountQuery = method.isCountQuery(); + this.isExistsQuery = method.isExistsQuery(); + this.isDeleteQuery = method.isDeleteQuery(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + this.namedQueries = namedQueries; + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#createQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor) + */ + @Override + protected Query createQuery(ParametersParameterAccessor accessor) { + + // StringN1qlQueryCreator creator = new StringN1qlQueryCreator( accessor, getQueryMethod()); + StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(), + getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider, + namedQueries); + Query query = creator.createQuery(); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Created query %s for * fields.", query.export() /*, query.getFieldsObject()*/)); + } + + return query; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isLimiting() + */ + @Override + protected boolean isLimiting() { + // TODO + return false; + } + + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, + boolean isDeleteQuery) { + return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java new file mode 100644 index 000000000..3c969e88c --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java @@ -0,0 +1,139 @@ +/* + * Copyright 2011-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.repository.query; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; + +/** + * Query to use a plain JSON String to create the {@link Query} to actually execute. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @author Thomas Darimont + * @author Mark Paluch + * @author Michael Reiche + */ +public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery { + + private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; + private static final Logger LOG = LoggerFactory.getLogger(StringBasedCouchbaseQuery.class); + + private final SpelExpressionParser expressionParser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + private final boolean isCountQuery; + private final boolean isExistsQuery; + private final boolean isDeleteQuery; + private final NamedQueries namedQueries; + + /** + * Creates a new {@link StringBasedCouchbaseQuery} for the given {@link String}, {@link CouchbaseQueryMethod}, + * {@link CouchbaseOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}. + * + * @param method must not be {@literal null}. + * @param couchbaseOperations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null} + * @param namedQueries + */ + public StringBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations couchbaseOperations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider, + NamedQueries namedQueries) { + + super(method, couchbaseOperations, expressionParser, evaluationContextProvider); + + Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); + + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; + this.isCountQuery = method.isCountQuery(); + this.isExistsQuery = method.isExistsQuery(); + this.isDeleteQuery = method.isDeleteQuery(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + this.namedQueries = namedQueries; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#createQuery(org.springframework.data.couchbase.repository.query.ConvertingParameterAccessor) + */ + @Override + protected Query createQuery(ParametersParameterAccessor accessor) { + + StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(), + getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider, + namedQueries); + Query query = creator.createQuery(); + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Created query %s for * fields.", query.export() /*, query.getFieldsObject()*/)); + } + + return query; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isCountQuery() + */ + @Override + protected boolean isCountQuery() { + return isCountQuery; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return isExistsQuery; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isDeleteQuery() + */ + @Override + protected boolean isDeleteQuery() { + return this.isDeleteQuery; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isLimiting() + */ + @Override + protected boolean isLimiting() { + return false; + } + + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, + boolean isDeleteQuery) { + return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java index df3c4cdaf..7f1eae6ef 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java @@ -52,15 +52,13 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator context; private final SpelExpressionParser parser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; private final StringBasedN1qlQueryParser queryParser; private final QueryMethod queryMethod; private final CouchbaseConverter couchbaseConverter; - private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); private final N1QLExpression parsedExpression; public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMethod queryMethod, - CouchbaseConverter couchbaseConverter, String bucketName, + CouchbaseConverter couchbaseConverter, String bucketName, SpelExpressionParser spelParser, QueryMethodEvaluationContextProvider evaluationContextProvider, NamedQueries namedQueries) { // AbstractQueryCreator needs a PartTree, so we give it a dummy one. @@ -73,7 +71,6 @@ public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMe this.context = couchbaseConverter.getMappingContext(); this.queryMethod = queryMethod; this.couchbaseConverter = couchbaseConverter; - this.evaluationContextProvider = evaluationContextProvider; final String namedQueryName = queryMethod.getNamedQueryName(); String queryString; if (queryMethod.hasInlineN1qlQuery()) { @@ -84,8 +81,8 @@ public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMe throw new IllegalArgumentException("query has no inline Query or named Query not found"); } this.queryParser = new StringBasedN1qlQueryParser(queryString, queryMethod, bucketName, couchbaseConverter, - getTypeField(), getTypeValue(), accessor, SPEL_PARSER, evaluationContextProvider); - this.parser = SPEL_PARSER; + getTypeField(), getTypeValue(), accessor, spelParser, evaluationContextProvider); + this.parser = spelParser; this.parsedExpression = this.queryParser.parsedExpression; } @@ -103,8 +100,7 @@ protected String getTypeValue() { @Override protected QueryCriteria create(final Part part, final Iterator iterator) { - PersistentPropertyPath path = context.getPersistentPropertyPath( - part.getProperty()); + PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); return from(part, property, where(path.toDotPath()), iterator); } @@ -115,8 +111,7 @@ protected QueryCriteria and(final Part part, final QueryCriteria base, final Ite return create(part, iterator); } - PersistentPropertyPath path = context.getPersistentPropertyPath( - part.getProperty()); + PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); return from(part, property, base.and(path.toDotPath()), iterator); @@ -139,15 +134,15 @@ protected Query complete(QueryCriteria criteria, Sort sort) { return q; } - private QueryCriteria from(final Part part, final CouchbasePersistentProperty property, - final QueryCriteria criteria, final Iterator parameters) { + private QueryCriteria from(final Part part, final CouchbasePersistentProperty property, final QueryCriteria criteria, + final Iterator parameters) { final Part.Type type = part.getType(); switch (type) { - case SIMPLE_PROPERTY: - return criteria; // this will be the dummy from PartTree - default: - throw new IllegalArgumentException("Unsupported keyword!"); + case SIMPLE_PROPERTY: + return criteria; // .eq(parameters.next()); // this will be the dummy from PartTree + default: + throw new IllegalArgumentException("Unsupported keyword!"); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 747c9d76e..a02f3ec2c 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -23,10 +23,9 @@ import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; -import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery; +import org.springframework.data.couchbase.repository.query.*; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -89,14 +88,14 @@ public void setBeanClassLoader(ClassLoader classLoader) { * Returns entity information based on the domain class. * * @param domainClass the class for the entity. - * @param the value type - * @param the id type. + * @param the value type + * @param the id type. * @return entity information for that domain class. */ @Override public CouchbaseEntityInformation getEntityInformation(Class domainClass) { - CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext.getRequiredPersistentEntity( - domainClass); + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext + .getRequiredPersistentEntity(domainClass); return new MappingCouchbaseEntityInformation<>(entity); } @@ -152,29 +151,18 @@ public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluat @Override public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata, final ProjectionFactory factory, final NamedQueries namedQueries) { - final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( - metadata.getRepositoryInterface(), metadata.getDomainType()); + final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping + .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - return new CouchbaseRepositoryQuery(couchbaseOperations, queryMethod, namedQueries); - /*CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - String namedQueryName = queryMethod.getNamedQueryName(); - if (queryMethod.hasN1qlAnnotation()) { - - if (queryMethod.hasInlineN1qlQuery()) { - return new StringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations, - SPEL_PARSER, evaluationContextProvider); - } else if (namedQueries.hasQuery(namedQueryName)) { - String namedQuery = namedQueries.getQuery(namedQueryName); - return new StringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations, SPEL_PARSER, - evaluationContextProvider); - } - + return new StringBasedCouchbaseQuery(queryMethod, couchbaseOperations, new SpelExpressionParser(), + evaluationContextProvider, namedQueries); + } else { + return new PartTreeCouchbaseQuery(queryMethod, couchbaseOperations, new SpelExpressionParser(), + evaluationContextProvider); } - - return new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations);*/ } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java index 5cc49c1ff..d15094f9b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java @@ -23,9 +23,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; -import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseRepositoryQuery; +import org.springframework.data.couchbase.repository.query.*; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -33,6 +31,7 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -84,14 +83,14 @@ public void setBeanClassLoader(ClassLoader classLoader) { * Returns entity information based on the domain class. * * @param domainClass the class for the entity. - * @param the value type - * @param the id type. + * @param the value type + * @param the id type. * @return entity information for that domain class. */ @Override public CouchbaseEntityInformation getEntityInformation(Class domainClass) { - CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext.getRequiredPersistentEntity( - domainClass); + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext + .getRequiredPersistentEntity(domainClass); return new MappingCouchbaseEntityInformation<>(entity); } @@ -106,8 +105,8 @@ public CouchbaseEntityInformation getEntityInformation(Class d */ @Override protected final Object getTargetRepository(final RepositoryInformation metadata) { - ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( - metadata.getRepositoryInterface(), metadata.getDomainType()); + ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping + .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); SimpleReactiveCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation, couchbaseOperations); @@ -149,10 +148,18 @@ public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluat @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { - final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( - metadata.getRepositoryInterface(), metadata.getDomainType()); - return new ReactiveCouchbaseRepositoryQuery(couchbaseOperations, - new CouchbaseQueryMethod(method, metadata, factory, mappingContext), namedQueries); + final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping + .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); + ReactiveCouchbaseQueryMethod queryMethod = new ReactiveCouchbaseQueryMethod(method, metadata, factory, + mappingContext); + + if (queryMethod.hasN1qlAnnotation()) { + return new ReactiveStringBasedCouchbaseQuery(queryMethod, couchbaseOperations, new SpelExpressionParser(), + evaluationContextProvider, namedQueries); + } else { + return new ReactivePartTreeCouchbaseQuery(queryMethod, couchbaseOperations, new SpelExpressionParser(), + evaluationContextProvider); + } } } 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 9d27fc0b7..10a4bdae6 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -18,13 +18,16 @@ import java.util.List; -import com.couchbase.client.java.query.QueryScanConsistency; 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 * @@ -39,6 +42,7 @@ public interface AirportRepository extends PagingAndSortingRepository findAll(); @Override + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Airport save(Airport airport); @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) @@ -47,6 +51,9 @@ public interface AirportRepository extends PagingAndSortingRepository findAllByIataNot(String iatas, Pageable pageable); + @Query("#{#n1ql.selectEntity} where iata = $1") @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List getAllByIata(String iata); 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 5d707cf1d..755f144e9 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java @@ -16,7 +16,12 @@ package org.springframework.data.couchbase.domain; + import org.springframework.data.couchbase.repository.Query; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import reactor.core.publisher.Flux; import org.springframework.data.couchbase.repository.ScanConsistency; @@ -26,6 +31,8 @@ import com.couchbase.client.java.query.QueryScanConsistency; +import java.util.ArrayList; + /** * template class for Reactive Couchbase operations * @@ -62,4 +69,21 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository findById(String var1); + + // use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<> + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Flux findAllByIataLike(String iata, final PageRequest page); + + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Mono findByIata(String iata); + + // This is not efficient. See findAllByIataLike for efficient reactive paging + default public Mono> findAllAirportsPaged(Pageable pageable) { + return count().flatMap(airportCount -> { + return findAll(pageable.getSort()) + .buffer(pageable.getPageSize(), (pageable.getPageNumber() * pageable.getPageSize())) + .elementAt(pageable.getPageNumber(), new ArrayList<>()) + .map(airports -> new PageImpl(airports, pageable, airportCount)); + }); + } } 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 4c14ce1d2..b8916368e 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -51,6 +51,9 @@ import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; import org.springframework.data.util.StreamUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.couchbase.client.core.error.IndexExistsException; @@ -141,13 +144,19 @@ void findBySimpleProperty() { Airport vie = null; try { vie = new Airport("airports::vie", "vie", "loww"); - airportRepository.save(vie); + vie = airportRepository.save(vie); List airports = airportRepository.findAllByIata("vie"); - assertEquals(vie.getId(), airports.get(0).getId()); + assertEquals(1, airports.size()); + System.out.println("findAllByIata(0): " + airports.get(0)); + Airport airport1 = airportRepository.findById(airports.get(0).getId()).get(); + assertEquals(airport1.getIata(), vie.getIata()); + System.out.println("findById: " + airport1); + Airport airport2 = airportRepository.findByIata(airports.get(0).getIata()); + assertEquals(airport1.getId(), vie.getId()); + System.out.println("findByIata: " + airport2); } finally { airportRepository.delete(vie); } - } @Test @@ -178,6 +187,11 @@ void count() { Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false); assertEquals(1, count); + Pageable pageable = PageRequest.of(0, 2); + Page aPage = airportRepository.findAllByIataNot("JFK", pageable); + assertEquals(iatas.length - 1, aPage.getTotalElements()); + assertEquals(pageable.getPageSize(), aPage.getContent().size()); + long airportCount = airportRepository.count(); assertEquals(7, airportCount); 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 2e0ed241d..4203b3d9a 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java @@ -25,7 +25,10 @@ import reactor.core.publisher.Flux; import reactor.test.StepVerifier; +import java.time.Duration; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -49,9 +52,14 @@ import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.couchbase.client.core.error.IndexExistsException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; /** * template class for Reactive Couchbase operations @@ -102,7 +110,17 @@ void findBySimpleProperty() { List airports1 = airportRepository.findAllByIata("vie").collectList().block(); assertEquals(1, airports1.size()); List airports2 = airportRepository.findAllByIata("vie").collectList().block(); - assertEquals(1, airports2.size()); + assertEquals(1,airports2.size()); + vie = airportRepository.save(vie).block(); + List airports = airportRepository.findAllByIata("vie").collectList().block(); + assertEquals(1, airports.size()); + System.out.println("findAllByIata(0): " + airports.get(0)); + Airport airport1 = airportRepository.findById(airports.get(0).getId()).block(); + assertEquals(airport1.getIata(), vie.getIata()); + System.out.println("findById: " + airport1); + Airport airport2 = airportRepository.findByIata(airports.get(0).getIata()).block(); + assertEquals(airport1.getId(), vie.getId()); + System.out.println("findByIata: " + airport2); } finally { airportRepository.delete(vie).block(); } @@ -121,18 +139,37 @@ public void testCas() { @Test void count() { - String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; - Future[] future = new Future[iatas.length]; - ExecutorService executorService = Executors.newFixedThreadPool(iatas.length); + Set iatas = new HashSet(); + iatas.add("JFK"); + iatas.add("IAD"); + iatas.add("SFO"); + iatas.add("SJC"); + iatas.add("SEA"); + iatas.add("LAX"); + iatas.add("PHX"); + Future[] future = new Future[iatas.size()]; + ExecutorService executorService = Executors.newFixedThreadPool(iatas.size()); try { - Callable[] suppliers = new Callable[iatas.length]; - for (int i = 0; i < iatas.length; i++) { - Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i].toLowerCase() /* lcao */); + + Callable[] suppliers = new Callable[iatas.size()]; + for (String iata : iatas) { + Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */); airportRepository.save(airport).block(); } + int page = 0; + + airportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) // + .expectNextMatches(a -> { + System.out.println(a); + return iatas.contains(a.getIata()); + }).expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete(); + + airportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) // + .expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete(); + Long airportCount = airportRepository.count().block(); - assertEquals(iatas.length, airportCount); + assertEquals(iatas.size(), airportCount); airportCount = airportRepository.countByIataIn("JFK", "IAD", "SFO").block(); assertEquals(3, airportCount); @@ -144,8 +181,8 @@ void count() { assertEquals(0, airportCount); } finally { - for (int i = 0; i < iatas.length; i++) { - Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i] /* lcao */); + for (String iata : iatas) { + Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */); try { airportRepository.delete(airport).block(); } catch (DataRetrievalFailureException drfe) { diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java index 4129771ed..499831c7a 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java @@ -38,6 +38,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; import org.springframework.data.repository.query.*; +import org.springframework.expression.spel.standard.SpelExpressionParser; import java.lang.reflect.Method; import java.util.Properties; @@ -75,7 +76,7 @@ void createsQueryCorrectly() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"), - queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(),QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); assertEquals( @@ -93,7 +94,7 @@ void createsQueryCorrectly2() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"), - queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser() ,QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); assertEquals( @@ -112,7 +113,7 @@ void wrongNumberArgs() throws Exception { try { StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"), - queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser() , QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); } catch (IllegalArgumentException e) { return; } @@ -129,7 +130,7 @@ void doesNotHaveAnnotation() throws Exception { try { StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"), - queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); } catch (IllegalArgumentException e) { return; } 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 9681255d4..6b37375f5 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 @@ -49,6 +49,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; import org.springframework.data.repository.query.*; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** @@ -87,7 +88,7 @@ void findUsingStringNq1l() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"), - queryMethod, converter, config().bucketname(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, config().bucketname(), new SpelExpressionParser(),QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); System.out.println(query.toN1qlSelectString(couchbaseTemplate.reactive(), Airline.class, false)); From 256c7a25533421e0728e0620682983e4c419f656 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Thu, 23 Jul 2020 17:11:37 -0700 Subject: [PATCH 2/3] DATACOUCH-588 - Implement pageable and realign repo query This implements pageable for non-reactive queries and realigns reactive queries with other spring-data projects to facilitate the implementaion of pageable (done) and other types of queries such as projection and distinct (not yet done) --- .../ExecutableExistsByIdOperationSupport.java | 7 +-- ...utableFindByAnalyticsOperationSupport.java | 16 +++--- .../ExecutableFindByIdOperationSupport.java | 7 +-- .../core/ExecutableFindByQueryOperation.java | 13 ++++- ...ExecutableFindByQueryOperationSupport.java | 28 +++++++--- ...eFindFromReplicasByIdOperationSupport.java | 6 +-- .../ExecutableInsertByIdOperationSupport.java | 7 +-- .../ExecutableRemoveByIdOperationSupport.java | 7 +-- .../ExecutableRemoveByQueryOperation.java | 8 ++- ...ecutableRemoveByQueryOperationSupport.java | 21 +++++--- ...ExecutableReplaceByIdOperationSupport.java | 5 +- .../ExecutableUpsertByIdOperationSupport.java | 5 +- ...activeFindByAnalyticsOperationSupport.java | 1 - .../core/ReactiveFindByQueryOperation.java | 16 ++++-- .../ReactiveFindByQueryOperationSupport.java | 44 ++++++++------- ...eFindFromReplicasByIdOperationSupport.java | 2 +- .../ReactiveInsertByIdOperationSupport.java | 1 + .../core/ReactiveRemoveByIdOperation.java | 1 + .../core/ReactiveRemoveByQueryOperation.java | 8 ++- ...ReactiveRemoveByQueryOperationSupport.java | 4 ++ .../convert/MappingCouchbaseConverter.java | 10 +++- .../data/couchbase/core/query/Query.java | 14 ++--- .../query/AbstractCouchbaseQuery.java | 48 +++++++++++++---- .../query/AbstractReactiveCouchbaseQuery.java | 53 ++++++++++-------- .../repository/query/BooleanUtil.java | 6 +-- .../query/CouchbaseQueryExecution.java | 26 ++------- .../query/CouchbaseQueryMethod.java | 18 +------ .../query/DtoInstantiatingConverter.java | 3 +- .../repository/query/N1qlQueryCreator.java | 2 +- .../query/N1qlRepositoryQueryExecutor.java | 10 ++-- .../query/PartTreeCouchbaseQuery.java | 19 ++++--- .../query/PartTreeN1qlBasedQuery.java | 11 +++- .../ReactiveCouchbaseQueryExecution.java | 21 ++------ .../query/ReactiveCouchbaseQueryMethod.java | 54 +++++++------------ .../ReactiveCouchbaseRepositoryQuery.java | 1 - .../ReactiveN1qlRepositoryQueryExecutor.java | 21 ++------ .../query/ReactivePartTreeCouchbaseQuery.java | 12 +++-- .../query/ReactivePartTreeN1qlBasedQuery.java | 6 ++- .../ReactiveStringBasedCouchbaseQuery.java | 7 +-- .../query/StringBasedCouchbaseQuery.java | 7 +-- .../query/StringBasedN1qlQueryParser.java | 10 ++-- .../query/StringN1qlQueryCreator.java | 27 +++++----- .../support/CouchbaseRepositoryFactory.java | 6 ++- .../ReactiveCouchbaseRepositoryFactory.java | 6 ++- .../data/couchbase/domain/Airport.java | 6 +++ .../couchbase/domain/AirportRepository.java | 14 +++-- .../domain/ReactiveAirportRepository.java | 15 +++--- ...chbaseRepositoryQueryIntegrationTests.java | 5 +- ...chbaseRepositoryQueryIntegrationTests.java | 14 +++-- .../StringN1qlQueryCreatorMockedTests.java | 32 ++++++----- .../query/StringN1qlQueryCreatorTests.java | 14 +++-- 51 files changed, 386 insertions(+), 319 deletions(-) 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 b8a63a9ec..3841fa647 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import org.springframework.data.couchbase.core.ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport; + import java.util.Collection; import java.util.Map; @@ -36,12 +38,11 @@ public ExecutableExistsById existsById() { static class ExecutableExistsByIdSupport implements ExecutableExistsById { private final CouchbaseTemplate template; - private final ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport reactiveSupport; + private final ReactiveExistsByIdSupport reactiveSupport; ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String collection) { this.template = template; - this.reactiveSupport = new ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport(template.reactive(), - collection); + this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), collection); } @Override 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 0be1cb59b..2a3568a7c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java @@ -15,13 +15,14 @@ */ package org.springframework.data.couchbase.core; +import org.springframework.data.couchbase.core.ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport; + import java.util.List; import java.util.stream.Stream; -import com.couchbase.client.java.analytics.AnalyticsScanConsistency; -import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.data.couchbase.core.query.AnalyticsQuery; -import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.analytics.AnalyticsScanConsistency; public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation { @@ -35,14 +36,15 @@ 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, ALL_QUERY, + AnalyticsScanConsistency.NOT_BOUNDED); } static class ExecutableFindByAnalyticsSupport implements ExecutableFindByAnalytics { private final CouchbaseTemplate template; private final Class domainType; - private final ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport reactiveSupport; + private final ReactiveFindByAnalyticsSupport reactiveSupport; private final AnalyticsQuery query; private final AnalyticsScanConsistency scanConsistency; @@ -51,8 +53,8 @@ static class ExecutableFindByAnalyticsSupport implements ExecutableFindByAnal this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<>( - template.reactive(), domainType, query, scanConsistency); + this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, query, + scanConsistency); this.scanConsistency = scanConsistency; } 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 ad4a333b2..8f8ca26a6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport; + import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -40,15 +42,14 @@ static class ExecutableFindByIdSupport implements ExecutableFindById { private final Class domainType; private final String collection; private final List fields; - private final ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport reactiveSupport; + private final ReactiveFindByIdSupport reactiveSupport; ExecutableFindByIdSupport(CouchbaseTemplate template, Class domainType, String collection, List fields) { this.template = template; this.domainType = domainType; this.collection = collection; this.fields = fields; - this.reactiveSupport = new ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<>(template.reactive(), - domainType, collection, fields); + this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields); } @Override 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 5078d7019..883b6e91f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java @@ -130,6 +130,17 @@ interface FindByQueryConsistentWith extends FindByQueryWithQuery { } - interface ExecutableFindByQuery extends FindByQueryConsistentWith {} + interface FindByQueryInCollection extends FindByQueryConsistentWith { + + /** + * Allows to override the default scan consistency. + * + * @param collection the collection to use for this query. + */ + FindByQueryInCollection inCollection(String collection); + + } + + interface ExecutableFindByQuery extends FindByQueryInCollection {} } 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 f0ae9d679..8daaf0405 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -18,11 +18,17 @@ import java.util.List; import java.util.stream.Stream; +import org.springframework.data.couchbase.core.ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport; import org.springframework.data.couchbase.core.query.Query; import com.couchbase.client.java.query.QueryScanConsistency; -import org.springframework.data.couchbase.core.ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport; +/** + * {@link ExecutableFindByQueryOperation} implementations for Couchbase. + * + * @author Michael Nitschinger + * @author Michael Reiche + */ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQueryOperation { private static final Query ALL_QUERY = new Query(); @@ -35,7 +41,7 @@ public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableFindByQuery findByQuery(final Class domainType) { - return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, "_default._default"); } static class ExecutableFindByQuerySupport implements ExecutableFindByQuery { @@ -43,17 +49,18 @@ static class ExecutableFindByQuerySupport implements ExecutableFindByQuery private final CouchbaseTemplate template; private final Class domainType; private final Query query; - private final ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport reactiveSupport; + private final ReactiveFindByQuerySupport reactiveSupport; private final QueryScanConsistency scanConsistency; + private final String collection; ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class domainType, final Query query, - final QueryScanConsistency scanConsistency) { + final QueryScanConsistency scanConsistency, final String collection) { this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), - domainType, query, scanConsistency); + this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), domainType, query, scanConsistency, collection); this.scanConsistency = scanConsistency; + this.collection = collection; } @Override @@ -79,12 +86,17 @@ public TerminatingFindByQuery matching(final Query query) { } else { scanCons = scanConsistency; } - return new ExecutableFindByQuerySupport<>(template, domainType, query, scanCons); + return new ExecutableFindByQuerySupport<>(template, domainType, query, scanCons, collection); } @Override public FindByQueryConsistentWith consistentWith(final QueryScanConsistency scanConsistency) { - return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency); + return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency, collection); + } + + @Override + public FindByQueryInCollection inCollection(final String collection) { + return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency, collection); } @Override 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 54e7f2c87..beefe1046 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java @@ -17,6 +17,7 @@ import java.util.Collection; +import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport; import org.springframework.util.Assert; public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation { @@ -37,14 +38,13 @@ static class ExecutableFindFromReplicasByIdSupport implements ExecutableFindF private final CouchbaseTemplate template; private final Class domainType; private final String collection; - private final ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport reactiveSupport; + private final ReactiveFindFromReplicasByIdSupport reactiveSupport; ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class domainType, String collection) { this.template = template; this.domainType = domainType; this.collection = collection; - this.reactiveSupport = new ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<>( - template.reactive(), domainType, collection); + this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, collection); } @Override 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 18fa5b382..7405fad64 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java @@ -18,6 +18,7 @@ import java.time.Duration; import java.util.Collection; +import org.springframework.data.couchbase.core.ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport; import org.springframework.util.Assert; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -48,7 +49,7 @@ static class ExecutableInsertByIdSupport implements ExecutableInsertById { private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; - private final ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport reactiveSupport; + private final ReactiveInsertByIdSupport reactiveSupport; ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, @@ -60,8 +61,8 @@ static class ExecutableInsertByIdSupport implements ExecutableInsertById { this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; - this.reactiveSupport = new ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<>(template.reactive(), - domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); + this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, collection, persistTo, + replicateTo, durabilityLevel, expiry); } @Override 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 27fed458c..3d1f10778 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.List; +import org.springframework.data.couchbase.core.ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport; import org.springframework.util.Assert; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -44,7 +45,7 @@ static class ExecutableRemoveByIdSupport implements ExecutableRemoveById { private final PersistTo persistTo; private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; - private final ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport reactiveRemoveByIdSupport; + private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport; ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { @@ -53,8 +54,8 @@ static class ExecutableRemoveByIdSupport implements ExecutableRemoveById { this.persistTo = persistTo; this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; - this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport( - template.reactive(), collection, persistTo, replicateTo, durabilityLevel); + this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), collection, persistTo, + replicateTo, durabilityLevel); } @Override 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 397535e22..9b50fdedb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java @@ -43,6 +43,12 @@ interface RemoveByQueryConsistentWith extends RemoveByQueryWithQuery { } - interface ExecutableRemoveByQuery extends RemoveByQueryConsistentWith {} + interface RemoveByQueryInCollection extends RemoveByQueryConsistentWith { + + RemoveByQueryConsistentWith inCollection(String collection); + + } + + interface ExecutableRemoveByQuery extends RemoveByQueryInCollection {} } 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 f1c4eced5..5db441957 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java @@ -17,6 +17,7 @@ import java.util.List; +import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport; import org.springframework.data.couchbase.core.query.Query; import com.couchbase.client.java.query.QueryScanConsistency; @@ -33,7 +34,7 @@ public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) @Override public ExecutableRemoveByQuery removeByQuery(Class domainType) { - return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, "_default._default"); } static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuery { @@ -41,17 +42,19 @@ static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuer private final CouchbaseTemplate template; private final Class domainType; private final Query query; - private final ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport reactiveSupport; + private final ReactiveRemoveByQuerySupport reactiveSupport; private final QueryScanConsistency scanConsistency; + private final String collection; ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class domainType, final Query query, - final QueryScanConsistency scanConsistency) { + final QueryScanConsistency scanConsistency, String collection) { this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<>( - template.reactive(), domainType, query, scanConsistency); + this.reactiveSupport = new ReactiveRemoveByQuerySupport<>(template.reactive(), domainType, query, + scanConsistency); this.scanConsistency = scanConsistency; + this.collection = collection; } @Override @@ -61,12 +64,16 @@ public List all() { @Override public TerminatingRemoveByQuery matching(final Query query) { - return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } @Override public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { - return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); + } + @Override + public RemoveByQueryInCollection inCollection(final String collection) { + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } } 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 888c998f4..399fa6969 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -19,6 +19,7 @@ import java.util.Collection; import org.springframework.util.Assert; +import org.springframework.data.couchbase.core.ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport; import com.couchbase.client.core.msg.kv.DurabilityLevel; import com.couchbase.client.java.kv.PersistTo; @@ -48,7 +49,7 @@ static class ExecutableReplaceByIdSupport implements ExecutableReplaceById private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; - private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport reactiveSupport; + private final ReactiveReplaceByIdSupport reactiveSupport; ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, @@ -60,7 +61,7 @@ static class ExecutableReplaceByIdSupport implements ExecutableReplaceById this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; - this.reactiveSupport = new ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<>(template.reactive(), + this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(), domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); } 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 3c4c70c67..9ec260ce8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java @@ -19,6 +19,7 @@ import java.util.Collection; import org.springframework.util.Assert; +import org.springframework.data.couchbase.core.ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport; import com.couchbase.client.core.msg.kv.DurabilityLevel; import com.couchbase.client.java.kv.PersistTo; @@ -48,7 +49,7 @@ static class ExecutableUpsertByIdSupport implements ExecutableUpsertById { private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; - private final ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport reactiveSupport; + private final ReactiveUpsertByIdSupport reactiveSupport; ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, @@ -60,7 +61,7 @@ static class ExecutableUpsertByIdSupport implements ExecutableUpsertById { this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; - this.reactiveSupport = new ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<>(template.reactive(), + this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(), domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); } 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 598048ba5..73de94c30 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java @@ -17,7 +17,6 @@ import com.couchbase.client.java.analytics.AnalyticsOptions; import com.couchbase.client.java.analytics.AnalyticsScanConsistency; -import com.couchbase.client.java.query.QueryOptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; 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 a414b1cbc..1a134dc6c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -15,14 +15,20 @@ */ package org.springframework.data.couchbase.core; -import org.springframework.dao.IncorrectResultSizeDataAccessException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.couchbase.core.query.Query; import com.couchbase.client.java.query.QueryScanConsistency; +/** + * ReactiveFindByQueryOperation + * + * @author Michael Nitschinger + * @author Michael Reiche + */ public interface ReactiveFindByQueryOperation { /** @@ -107,7 +113,7 @@ interface FindByQueryConsistentWith extends FindByQueryWithQuery { /** * Collection override (optional). */ - interface FindWithCollection extends FindByQueryWithQuery { + interface FindInCollection extends FindByQueryWithQuery { /** * Explicitly set the name of the collection to perform the query on.
@@ -117,13 +123,13 @@ interface FindWithCollection extends FindByQueryWithQuery { * @return new instance of {@link FindWithProjection}. * @throws IllegalArgumentException if collection is {@literal null}. */ - FindWithProjection inCollection(String collection); + FindInCollection inCollection(String collection); } /** * Result type override (optional). */ - interface FindWithProjection extends FindByQueryWithQuery, FindDistinct { + interface FindWithProjection extends FindInCollection, FindDistinct { /** * Define the target type fields should be mapped to.
@@ -227,6 +233,6 @@ interface TerminatingDistinct extends DistinctWithQuery { Flux all(); } - interface ReactiveFindByQuery extends FindByQueryConsistentWith, FindWithCollection, FindDistinct {} + interface ReactiveFindByQuery extends FindByQueryConsistentWith, FindInCollection, FindDistinct {} } 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 53ff56562..717672791 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -15,16 +15,18 @@ */ package org.springframework.data.couchbase.core; -import org.springframework.data.couchbase.core.support.TemplateUtils; -import org.springframework.data.couchbase.core.query.Query; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.couchbase.core.support.TemplateUtils; + import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.ReactiveQueryResult; /** + * {@link ReactiveFindByQueryOperation} implementations for Couchbase. + * * @author Michael Nitschinger * @author Michael Reiche */ @@ -40,7 +42,8 @@ public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate templ @Override public ReactiveFindByQuery findByQuery(final Class domainType) { - return new ReactiveFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + return new ReactiveFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, + "_default._default"); } static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { @@ -49,13 +52,15 @@ static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { private final Class domainType; private final Query query; private final QueryScanConsistency scanConsistency; + private final String collection; ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, final Query query, - final QueryScanConsistency scanConsistency) { + final QueryScanConsistency scanConsistency, final String collection) { this.template = template; this.domainType = domainType; this.query = query; this.scanConsistency = scanConsistency; + this.collection = collection; } @Override @@ -66,12 +71,22 @@ public TerminatingFindByQuery matching(Query query) { } else { scanCons = scanConsistency; } - return new ReactiveFindByQuerySupport<>(template, domainType, query, scanCons); + return new ReactiveFindByQuerySupport<>(template, domainType, query, scanCons, collection); } @Override public FindByQueryConsistentWith consistentWith(QueryScanConsistency scanConsistency) { - return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency, collection); + } + + @Override + public FindInCollection inCollection(String collection) { + return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency, collection); + } + + @Override + public TerminatingDistinct distinct(String field) { + throw new RuntimeException(("not implemented")); } @Override @@ -116,8 +131,9 @@ public Mono count() { } else { return throwable; } - }).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong(TemplateUtils.SELECT_COUNT)) - .next(); + }).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> { + return row.getLong(TemplateUtils.SELECT_COUNT); + }).next(); }); } @@ -129,16 +145,6 @@ public Mono exists() { private String assembleEntityQuery(final boolean count) { return query.toN1qlSelectString(template, this.domainType, count); } - - @Override - public FindWithProjection inCollection(String collection) { - throw new RuntimeException(("not implemented")); - } - - @Override - public TerminatingDistinct distinct(String field) { - throw new RuntimeException(("not implemented")); - } } } 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 7124927ab..59bb0c54b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java @@ -15,7 +15,7 @@ */ package org.springframework.data.couchbase.core; -import static com.couchbase.client.java.kv.GetAnyReplicaOptions.*; +import static com.couchbase.client.java.kv.GetAnyReplicaOptions.getAnyReplicaOptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; 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 ebf6d0daa..3898fcd82 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -22,6 +22,7 @@ import java.util.Collection; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.util.Assert; import com.couchbase.client.core.msg.kv.DurabilityLevel; 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 d1cc5fa65..4a9897ea8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java @@ -49,6 +49,7 @@ interface RemoveByIdWithDurability extends RemoveByIdWithCollection, WithDurabil } + interface ReactiveRemoveById extends RemoveByIdWithDurability {} } 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 de837248e..8518088d9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java @@ -41,6 +41,12 @@ interface RemoveByQueryConsistentWith extends RemoveByQueryWithQuery { } - interface ReactiveRemoveByQuery extends RemoveByQueryConsistentWith {} + interface RemoveByQueryInCollection extends RemoveByQueryConsistentWith { + + RemoveByQueryConsistentWith inCollection(String collection); + + } + + interface ReactiveRemoveByQuery extends RemoveByQueryInCollection {} } 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 cb2c277cd..5b96d1c5c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -91,6 +91,10 @@ public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanC return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); } + @Override + public RemoveByQueryConsistentWith inCollection(final String collection) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + } private String assembleDeleteQuery() { return query.toN1qlRemoveString(template, this.domainType); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index 4ab824f75..9b76c0d1d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -16,7 +16,8 @@ package org.springframework.data.couchbase.core.convert; -import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*; +import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.UNIQUE; +import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.USE_ATTRIBUTES; import java.util.ArrayList; import java.util.Collection; @@ -45,8 +46,13 @@ import org.springframework.data.couchbase.core.mapping.id.IdPrefix; import org.springframework.data.couchbase.core.mapping.id.IdSuffix; import org.springframework.data.couchbase.core.query.N1qlJoin; -import org.springframework.data.mapping.*; +import org.springframework.data.mapping.Alias; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.AssociationHandler; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index d19788586..afcf8ad7b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -20,11 +20,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.couchbase.client.java.json.JsonArray; -import com.couchbase.client.java.json.JsonObject; -import com.couchbase.client.java.json.JsonValue; -import com.couchbase.client.java.query.QueryOptions; -import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; @@ -37,7 +32,15 @@ import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryScanConsistency; + /** + * Couchbase Query + * * @author Michael Nitschinger * @author Michael Reiche */ @@ -147,7 +150,6 @@ public QueryScanConsistency getScanConsistency() { return queryScanConsistency; } - /** * Sets the given scan consistency on the {@link Query} instance. * 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 6482866b3..995dd70e9 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 @@ -1,5 +1,23 @@ +/* + * Copyright 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.couchbase.repository.query; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.CouchbaseOperations; @@ -7,21 +25,29 @@ import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.repository.core.EntityMetadata; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +/** + * {@link RepositoryQuery} implementation for Couchbase. + * + * @author Michael Reiche + * @since 4.1 + */ public abstract class AbstractCouchbaseQuery implements RepositoryQuery { private final CouchbaseQueryMethod method; private final CouchbaseOperations operations; private final EntityInstantiators instantiators; - // private final FindWithProjection findOperationWithProjection; + // private final FindWithProjection findOperationWithProjection; // TODO private final ExecutableFindByQueryOperation.ExecutableFindByQuery findOperationWithProjection; private final SpelExpressionParser expressionParser; private final QueryMethodEvaluationContextProvider evaluationContextProvider; @@ -117,13 +143,13 @@ protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processo Query query = createQuery(accessor); query = applyAnnotatedConsistencyIfPresent(query); - // query = applyAnnotatedCollationIfPresent(query, accessor); + // query = applyAnnotatedCollationIfPresent(query, accessor); // TODO ExecutableFindByQueryOperation.ExecutableFindByQuery find = typeToRead == null // ? findOperationWithProjection // : findOperationWithProjection; // TODO .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // TODO CouchbaseQueryExecution execution = getExecution(accessor, new CouchbaseQueryExecution.ResultProcessingConverter(processor, getOperations(), instantiators), find); @@ -148,8 +174,10 @@ private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, if (isDeleteQuery()) { return new CouchbaseQueryExecution.DeleteExecution(getOperations(), method); - // } else if (isTailable(method)) { - // return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); + /* // TODO + } else if (isTailable(method)) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); + */ } else if (method.isCollectionQuery()) { return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); } else if (isCountQuery()) { @@ -172,8 +200,8 @@ private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, } } - private boolean isTailable(ReactiveCouchbaseQueryMethod method) { - return false; // method.getTailableAnnotation() != null; + private boolean isTailable(CouchbaseQueryMethod method) { + return false; // method.getTailableAnnotation() != null; // TODO } /** 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 2b90d6557..fff5efa97 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 @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,26 @@ */ package org.springframework.data.couchbase.repository.query; -import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; -import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; -import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation.ReactiveFindByQuery; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.repository.core.EntityMetadata; -import org.springframework.data.repository.query.*; -import org.springframework.data.util.ClassTypeInformation; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; -import org.springframework.data.mapping.model.EntityInstantiators; - +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; +import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation.ReactiveFindByQuery; +import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.DeleteExecution; +import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.ResultProcessingConverter; +import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.ResultProcessingExecution; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.repository.core.EntityMetadata; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; @@ -38,16 +43,15 @@ /** * Base class for reactive {@link RepositoryQuery} implementations for Couchbase. * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.0 + * @author Michael Reiche + * @since 4.1 */ public abstract class AbstractReactiveCouchbaseQuery implements RepositoryQuery { private final ReactiveCouchbaseQueryMethod method; private final ReactiveCouchbaseOperations operations; private final EntityInstantiators instantiators; - // private final FindWithProjection findOperationWithProjection; + // private final FindWithProjection findOperationWithProjection; // TODO private final ReactiveFindByQuery findOperationWithProjection; private final SpelExpressionParser expressionParser; private final QueryMethodEvaluationContextProvider evaluationContextProvider; @@ -143,16 +147,16 @@ protected Object doExecute(ReactiveCouchbaseQueryMethod method, ResultProcessor Query query = createQuery(accessor); query = applyAnnotatedConsistencyIfPresent(query); - // query = applyAnnotatedCollationIfPresent(query, accessor); + // query = applyAnnotatedCollationIfPresent(query, accessor); // TODO ReactiveFindByQueryOperation.FindByQueryWithQuery find = typeToRead == null // ? findOperationWithProjection // : findOperationWithProjection; // TODO .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // TODO ReactiveCouchbaseQueryExecution execution = getExecution(accessor, - new ReactiveCouchbaseQueryExecution.ResultProcessingConverter(processor, getOperations(), instantiators), find); + new ResultProcessingConverter(processor, getOperations(), instantiators), find); return execution.execute(query, processor.getReturnedType().getDomainType(), collection); } @@ -165,7 +169,7 @@ protected Object doExecute(ReactiveCouchbaseQueryMethod method, ResultProcessor */ private ReactiveCouchbaseQueryExecution getExecution(ParameterAccessor accessor, Converter resultProcessing, ReactiveFindByQueryOperation.FindByQueryWithQuery operation) { - return new ReactiveCouchbaseQueryExecution.ResultProcessingExecution(getExecutionToWrap(accessor, operation), + return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing); } @@ -174,9 +178,12 @@ private ReactiveCouchbaseQueryExecution getExecutionToWrap(ParameterAccessor acc if (isDeleteQuery()) { return new DeleteExecution(getOperations(), method); - // } else if (isTailable(method)) { - // return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); - } else if (method.isCollectionQuery()) { + /* TODO + } else if (isTailable(method)) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); + this.metadata.getReturnType(this.method).getType() + */ + } else if (method.isCollectionQuery() /*|| method.getReturnedObjectType() instanceof Flux)*/) { return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); } else if (isCountQuery()) { return (q, t, c) -> operation.matching(q).count(); @@ -191,7 +198,7 @@ private ReactiveCouchbaseQueryExecution getExecutionToWrap(ParameterAccessor acc } private boolean isTailable(ReactiveCouchbaseQueryMethod method) { - return false; // method.getTailableAnnotation() != null; + return false; // method.getTailableAnnotation() != null; // TODO } /** @@ -219,7 +226,7 @@ Query applyAnnotatedConsistencyIfPresent(Query query) { * @return */ protected Query createCountQuery(ParametersParameterAccessor accessor) { - return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); + return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); // TODO } /** diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java b/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java index b7113e07b..90ee69e3e 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ /** * Utility class containing methods to interact with boolean values. * - * @author Mark Paluch - * @since 2.0.9 + * @author Michael Reiche + * @since 4.1 */ final class BooleanUtil { 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 79f1c93ed..95ac6abb4 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 @@ -15,6 +15,9 @@ */ package org.springframework.data.couchbase.repository.query; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import java.util.List; import org.reactivestreams.Publisher; @@ -31,18 +34,14 @@ import org.springframework.data.repository.support.PageableExecutionUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; /** * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a * {@link org.springframework.data.repository.query.QueryMethod} a {@link Query} can be executed in various flavors. * TODO: seems to be a lot of duplication with ReactiveCouchbaseQueryExecution * - * @author Oliver Gierke - * @author Mark Paluch - * @author Christoph Strobl * @author Michael Reiche + * @since 4.1 */ @FunctionalInterface interface CouchbaseQueryExecution { @@ -52,8 +51,6 @@ interface CouchbaseQueryExecution { /** * {@link CouchbaseQueryExecution} removing documents matching the query. * - * @author Mark Paluch - * @author Artyom Gabeev */ final class DeleteExecution implements CouchbaseQueryExecution { @@ -73,13 +70,7 @@ public DeleteExecution(CouchbaseOperations operations, CouchbaseQueryMethod meth @Override public Object execute(Query query, Class type, String collection) { - - // Class type = method.getEntityInformation().getJavaType(); - - // if (method.isCollectionQuery()) { - return operations.removeByQuery(type).matching(query).all(); - // } - + return operations.removeByQuery(type).inCollection(collection).matching(query).all(); } } @@ -111,7 +102,6 @@ public Object execute(Query query, Class type, String collection) { /** * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. * - * @author Mark Paluch */ final class ResultProcessingConverter implements Converter { @@ -173,9 +163,6 @@ static boolean isVoid(ReturnedType returnedType) { /** * {@link CouchbaseQueryExecution} for {@link Slice} query methods. * - * @author Oliver Gierke - * @author Christoph Strobl - * @since 1.5 */ final class SlicedExecution implements CouchbaseQueryExecution { @@ -214,9 +201,6 @@ public Object execute(Query query, Class type, String collection) { /** * {@link CouchbaseQueryExecution} for pagination queries. * - * @author Oliver Gierke - * @author Mark Paluch - * @author Christoph Strobl */ final class PagedExecution implements CouchbaseQueryExecution { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java index f53a5b6b3..a14195a96 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java @@ -43,34 +43,18 @@ * @author Michael Nitschinger * @author Simon Baslé * @author Oliver Gierke + * @author Michael Reiche */ public class CouchbaseQueryMethod extends QueryMethod { private final Method method; - private final Lazy isCollectionQueryCouchbase; // not to be confused with QueryMethod.isCollectionQuery public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, MappingContext, CouchbasePersistentProperty> mappingContext) { super(method, metadata, factory); this.method = method; - this.isCollectionQueryCouchbase = Lazy.of(() -> { - boolean result = !(isPageQuery() || isSliceQuery()) - && ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()); - return result; - }); - } - /* - * does this query return a collection? - * This must override QueryMethod.isCollection() as isCollectionQueryCouchbase is different from isCollectionQuery - * - * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryMethod#isCollection() - */ - @Override - public boolean isCollectionQuery() { - return (Boolean) this.isCollectionQueryCouchbase.get(); } /** diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java b/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java index 62b3d4478..d62767e48 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/DtoInstantiatingConverter.java @@ -33,9 +33,8 @@ /** * {@link Converter} to instantiate DTOs from fully equipped domain objects. * - * @author Oliver Gierke - * @author Mark Paluch * @author Michael Reiche + * @since 4.1 */ class DtoInstantiatingConverter implements Converter { 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 a10ac154d..be4b21375 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 @@ -15,7 +15,7 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.QueryCriteria.*; +import static org.springframework.data.couchbase.core.query.QueryCriteria.where; import java.util.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 e2d0d8963..a77e7071d 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 @@ -15,12 +15,9 @@ */ package org.springframework.data.couchbase.repository.query; -import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.query.ParameterAccessor; @@ -29,6 +26,8 @@ import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.spel.standard.SpelExpressionParser; +import com.couchbase.client.java.query.QueryScanConsistency; + /** * @author Michael Nitschinger * @author Michael Reiche @@ -48,6 +47,7 @@ public N1qlRepositoryQueryExecutor(final CouchbaseOperations operations, final C this.queryMethod = queryMethod; this.namedQueries = namedQueries; this.evaluationContextProvider = evaluationContextProvider; + throw new RuntimeException("Deprecated"); } private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); @@ -73,8 +73,8 @@ public Object execute(final Object[] parameters) { query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery(); } - //q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass) - // .consistentWith(buildQueryScanConsistency()).matching(query); + // q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass) + // .consistentWith(buildQueryScanConsistency()).matching(query); ExecutableFindByQueryOperation.ExecutableFindByQuery operation = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations .findByQuery(domainClass).consistentWith(buildQueryScanConsistency()); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java index 13c0436ce..37bb5346b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,17 +21,19 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.spel.standard.SpelExpressionParser; /** * {@link RepositoryQuery} implementation for Couchbase. * - * @author Oliver Gierke - * @author Christoph Strobl - * @author Thomas Darimont - * @author Mark Paluch + * @author Michael Reiche + * @since 4.1 */ public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery { @@ -55,11 +57,8 @@ public PartTreeCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations o super(method, operations, expressionParser, evaluationContextProvider); this.processor = method.getResultProcessor(); - // TODO - why the for loop? Why not just : - // this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()) for (PartTree.OrPart parts : this.tree = new PartTree(method.getName(), - processor.getReturnedType().getDomainType())) { - } + processor.getReturnedType().getDomainType())) {} this.context = operations.getConverter().getMappingContext(); this.converter = operations.getConverter(); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java index e3b524cdd..92c80316f 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java @@ -16,8 +16,12 @@ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.N1QLExpression.*; -import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.*; +import static org.springframework.data.couchbase.core.query.N1QLExpression.count; +import static org.springframework.data.couchbase.core.query.N1QLExpression.delete; +import static org.springframework.data.couchbase.core.query.N1QLExpression.i; +import static org.springframework.data.couchbase.core.query.N1QLExpression.select; +import static org.springframework.data.couchbase.core.query.N1QLExpression.x; +import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.createReturningExpressionForDelete; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.query.N1QLExpression; @@ -37,7 +41,9 @@ * @author Simon Baslé * @author Subhashni Balakrishnan * @author Mark Paluch + * @author Michael Reiche */ +@Deprecated public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { private final PartTree partTree; @@ -46,6 +52,7 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { public PartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { super(queryMethod, couchbaseOperations); this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); + throw new RuntimeException("Deprecated"); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java index 4106a0ffa..02281e0b0 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java @@ -20,9 +20,9 @@ import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; -import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.util.Assert; @@ -33,9 +33,8 @@ * {@link org.springframework.data.repository.query.QueryMethod} a {@link AbstractReactiveCouchbaseQuery} can be * executed in various flavors. * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.0 + * @author Michael Reiche + * @since 4.1 */ interface ReactiveCouchbaseQueryExecution { @@ -65,19 +64,7 @@ public DeleteExecution(ReactiveCouchbaseOperations operations, CouchbaseQueryMet @Override public Object execute(Query query, Class type, String collection) { - - // if (method.isCollectionQuery()) { - return operations.removeByQuery(type).matching(query).all(); - // } - - /* - if (method.isQueryForEntity() && !ClassUtils.isPrimitiveOrWrapper(method.getReturnedObjectType())) { - return operations.removeByQuery(query, type); - } - - return operations.removeByQuery(query, type, collection) - .map(deleteResult -> deleteResult.wasAcknowledged() ? deleteResult.getDeletedCount() : 0L); - */ + return operations.removeByQuery(type).inCollection(collection).matching(query).all(); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java index 13892db41..6e5419976 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.repository.util.ClassUtils.*; +import static org.springframework.data.repository.util.ClassUtils.hasParameterOfType; import java.lang.reflect.Method; @@ -38,9 +38,8 @@ /** * Reactive specific implementation of {@link CouchbaseQueryMethod}. * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.0 + * @author Michael Reiche + * @since 4.1 */ public class ReactiveCouchbaseQueryMethod extends CouchbaseQueryMethod { @@ -48,7 +47,7 @@ public class ReactiveCouchbaseQueryMethod extends CouchbaseQueryMethod { private static final ClassTypeInformation SLICE_TYPE = ClassTypeInformation.from(Slice.class); private final Method method; - //private final Lazy isCollectionQuery; + private final Lazy isCollectionQueryCouchbase; // not to be confused with QueryMethod.isCollectionQuery /** * Creates a new {@link ReactiveCouchbaseQueryMethod} from the given {@link Method}. @@ -91,45 +90,32 @@ public ReactiveCouchbaseQueryMethod(Method method, RepositoryMetadata metadata, } this.method = method; - + this.isCollectionQueryCouchbase = Lazy.of(() -> { + boolean result = !(isPageQuery() || isSliceQuery()) + && ReactiveWrappers.isMultiValueType(metadata.getReturnType(method).getType()); + return result; + }); } /* + * All reactive query methods are streaming queries. * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod#createParameters(java.lang.reflect.Method) - */ - /* - @Override - protected CouchbaseParameters createParameters(Method method) { - return new CouchbaseParameters(method, isGeoNearQuery(method)); - }*/ - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryMethod#isModifyingQuery() - */ - @Override - public boolean isModifyingQuery() { - return super.isModifyingQuery(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryMethod#isQueryForEntity() + * @see org.springframework.data.repository.query.QueryMethod#isStreamQuery() */ @Override - public boolean isQueryForEntity() { - return super.isQueryForEntity(); + public boolean isStreamQuery() { + return true; } /* - * All reactive query methods are streaming queries. + * does this query return a collection? + * This must override QueryMethod.isCollection() as isCollectionQueryCouchbase is different from isCollectionQuery + * * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryMethod#isStreamQuery() + * @see org.springframework.data.repository.query.QueryMethod#isCollection() */ @Override - public boolean isStreamQuery() { - return true; + public boolean isCollectionQuery() { + return (Boolean) this.isCollectionQueryCouchbase.get(); } - } 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 d2b6b40c5..85f8b0641 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 @@ -20,7 +20,6 @@ import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; /** 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 c15fa09df..084f1684d 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 @@ -15,21 +15,10 @@ */ package org.springframework.data.couchbase.repository.query; -import com.couchbase.client.java.query.QueryScanConsistency; -import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; -import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; -import org.springframework.data.domain.Pageable; -import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.repository.core.NamedQueries; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; -import reactor.core.publisher.Flux; - -import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.repository.query.parser.PartTree; - -import java.util.List; /** * @author Michael Nitschinger @@ -51,6 +40,7 @@ public ReactiveN1qlRepositoryQueryExecutor(final ReactiveCouchbaseOperations ope this.queryMethod = queryMethod; this.namedQueries = namedQueries; this.evaluationContextProvider = evaluationContextProvider; + throw new RuntimeException("Deprecated"); } /** @@ -60,12 +50,11 @@ public ReactiveN1qlRepositoryQueryExecutor(final ReactiveCouchbaseOperations ope * @return */ public Object execute(final Object[] parameters) { - // counterpart to N1qlRespositoryQueryExecutor, if (queryMethod.hasN1qlAnnotation()) { - return new ReactiveStringBasedCouchbaseQuery(queryMethod, operations, - new SpelExpressionParser(), evaluationContextProvider, namedQueries).execute(parameters); + return new ReactiveStringBasedCouchbaseQuery(queryMethod, operations, new SpelExpressionParser(), + evaluationContextProvider, namedQueries).execute(parameters); } else { return new ReactivePartTreeCouchbaseQuery(queryMethod, operations, new SpelExpressionParser(), evaluationContextProvider).execute(parameters); diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java index 598d814d8..22183841d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +21,18 @@ import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.spel.standard.SpelExpressionParser; /** * Reactive PartTree {@link RepositoryQuery} implementation for Couchbase. * - * @author Mark Paluch - * @author Christoph Strobl - * @since 2.0 + * @author Michael Reiche + * @since 4.1 */ public class ReactivePartTreeCouchbaseQuery extends AbstractReactiveCouchbaseQuery { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java index b82e60908..a452eaec6 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java @@ -15,7 +15,9 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.N1QLExpression.*; +import static org.springframework.data.couchbase.core.query.N1QLExpression.count; +import static org.springframework.data.couchbase.core.query.N1QLExpression.select; +import static org.springframework.data.couchbase.core.query.N1QLExpression.x; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.N1QLExpression; @@ -31,6 +33,7 @@ * A reactive {@link RepositoryQuery} for Couchbase, based on query derivation * * @author Subhashni Balakrishnan + * @author Michael Reiche * @since 3.0 * @deprecated */ @@ -43,6 +46,7 @@ public class ReactivePartTreeN1qlBasedQuery extends ReactiveAbstractN1qlBasedQue public ReactivePartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, ReactiveCouchbaseOperations operations) { super(queryMethod, operations); this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); + throw new RuntimeException("deprecated"); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java index f39847c9e..08170611a 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.Query; @@ -30,10 +29,8 @@ /** * Query to use a plain JSON String to create the {@link Query} to actually execute. * - * @author Mark Paluch - * @author Christoph Strobl * @author Michael Reiche - * @since 2.0 + * @since 4.1 */ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbaseQuery { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java index 3c969e88c..0411751c6 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,8 @@ /** * Query to use a plain JSON String to create the {@link Query} to actually execute. * - * @author Oliver Gierke - * @author Christoph Strobl - * @author Thomas Darimont - * @author Mark Paluch * @author Michael Reiche + * @since 4.1 */ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java index 3f3313038..cc2f9ca68 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java @@ -15,8 +15,10 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.N1QLExpression.*; -import static org.springframework.data.couchbase.core.support.TemplateUtils.*; +import static org.springframework.data.couchbase.core.query.N1QLExpression.i; +import static org.springframework.data.couchbase.core.query.N1QLExpression.x; +import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS; +import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID; import java.util.ArrayList; import java.util.Collection; @@ -25,7 +27,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.couchbase.client.core.error.InvalidArgumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; @@ -42,11 +43,12 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; +import com.couchbase.client.core.error.InvalidArgumentException; import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.json.JsonValue; -import org.springframework.util.Assert; /** * @author Subhashni Balakrishnan diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java index 7f1eae6ef..c4d6b2a82 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java @@ -15,37 +15,35 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.N1QLExpression.x; -import static org.springframework.data.couchbase.core.query.QueryCriteria.*; +import static org.springframework.data.couchbase.core.query.QueryCriteria.where; -import java.util.ArrayList; import java.util.Iterator; -import com.couchbase.client.java.json.JsonArray; -import com.couchbase.client.java.json.JsonObject; -import com.couchbase.client.java.json.JsonValue; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.query.QueryCriteria; import org.springframework.data.couchbase.core.query.StringQuery; -import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.core.NamedQueries; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.Assert; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; /** * @author Michael Reiche + * @since 4.1 */ public class StringN1qlQueryCreator extends AbstractQueryCreator { @@ -58,7 +56,8 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator findAllByIataNot(String iatas, Pageable pageable); - @Query("#{#n1ql.selectEntity} where iata = $1") @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) List getAllByIata(String iata); @@ -71,11 +68,12 @@ public interface AirportRepository extends PagingAndSortingRepository projectIds, @Param("planIds") List planIds, @Param("active") Boolean active); + Long countFancyExpression(@Param("projectIds") List projectIds, @Param("planIds") List planIds, + @Param("active") Boolean active); + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Page findAllByIataNot(String iata, Pageable pageable); } 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 755f144e9..63cb3388d 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java @@ -16,23 +16,22 @@ package org.springframework.data.couchbase.domain; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; import org.springframework.data.couchbase.repository.Query; +import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import reactor.core.publisher.Flux; - -import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.data.repository.reactive.ReactiveSortingRepository; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Mono; import com.couchbase.client.java.query.QueryScanConsistency; -import java.util.ArrayList; - /** * template class for Reactive Couchbase operations * @@ -74,6 +73,10 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository findAllByIataLike(String iata, final PageRequest page); + // use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<> + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Flux findAllByIataLike(String iata); + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Mono findByIata(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 b8916368e..b92847f82 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -40,11 +40,10 @@ import org.springframework.data.couchbase.domain.Address; import org.springframework.data.couchbase.domain.Airport; import org.springframework.data.couchbase.domain.AirportRepository; -import org.springframework.data.couchbase.domain.ReactiveUserRepository; -import org.springframework.data.couchbase.domain.User; -import org.springframework.data.couchbase.domain.UserRepository; 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.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.data.couchbase.util.Capabilities; import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; 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 4203b3d9a..819655dcf 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java @@ -22,10 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import reactor.core.publisher.Flux; import reactor.test.StepVerifier; -import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -52,14 +50,10 @@ import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.couchbase.client.core.error.IndexExistsException; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; /** * template class for Reactive Couchbase operations @@ -88,16 +82,21 @@ void beforeEach() { @Test void shouldSaveAndFindAll() { Airport vie = null; + Airport jfk = null; try { vie = new Airport("airports::vie", "vie", "loww"); airportRepository.save(vie).block(); + jfk = new Airport("airports::jfk", "JFK", "xxxx"); + airportRepository.save(jfk).block(); List all = airportRepository.findAll().toStream().collect(Collectors.toList()); assertFalse(all.isEmpty()); assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::vie"))); + assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::jfk"))); } finally { airportRepository.delete(vie).block(); + airportRepository.delete(jfk).block(); } } @@ -110,7 +109,7 @@ void findBySimpleProperty() { List airports1 = airportRepository.findAllByIata("vie").collectList().block(); assertEquals(1, airports1.size()); List airports2 = airportRepository.findAllByIata("vie").collectList().block(); - assertEquals(1,airports2.size()); + assertEquals(1, airports2.size()); vie = airportRepository.save(vie).block(); List airports = airportRepository.findAllByIata("vie").collectList().block(); assertEquals(1, airports.size()); @@ -161,7 +160,6 @@ void count() { airportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) // .expectNextMatches(a -> { - System.out.println(a); return iatas.contains(a.getIata()); }).expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete(); diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java index 499831c7a..a0de49fe0 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java @@ -15,6 +15,13 @@ */ package org.springframework.data.couchbase.repository.query; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; + +import java.lang.reflect.Method; +import java.util.Properties; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; @@ -37,16 +44,13 @@ import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.DefaultParameters; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; -import java.lang.reflect.Method; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; - /** * @author Michael Nitschinger * @author Michael Reiche @@ -76,7 +80,8 @@ void createsQueryCorrectly() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"), - queryMethod, converter, "travel-sample", new SpelExpressionParser(),QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(), + QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); assertEquals( @@ -94,7 +99,8 @@ void createsQueryCorrectly2() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"), - queryMethod, converter, "travel-sample", new SpelExpressionParser() ,QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(), + QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); assertEquals( @@ -113,7 +119,8 @@ void wrongNumberArgs() throws Exception { try { StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"), - queryMethod, converter, "travel-sample", new SpelExpressionParser() , QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(), + QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); } catch (IllegalArgumentException e) { return; } @@ -130,7 +137,8 @@ void doesNotHaveAnnotation() throws Exception { try { StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"), - queryMethod, converter, "travel-sample", new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, "travel-sample", new SpelExpressionParser(), + QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); } catch (IllegalArgumentException e) { return; } 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 6b37375f5..2b42b6fb0 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 @@ -15,7 +15,7 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; import java.lang.reflect.Method; @@ -37,7 +37,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.*; +import org.springframework.data.couchbase.domain.Airline; +import org.springframework.data.couchbase.domain.AirlineRepository; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.data.couchbase.util.Capabilities; import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; @@ -48,7 +49,11 @@ import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.DefaultParameters; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -88,7 +93,8 @@ void findUsingStringNq1l() throws Exception { converter.getMappingContext()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"), - queryMethod, converter, config().bucketname(), new SpelExpressionParser(),QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); + queryMethod, converter, config().bucketname(), new SpelExpressionParser(), + QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); System.out.println(query.toN1qlSelectString(couchbaseTemplate.reactive(), Airline.class, false)); From d9774a3dbbc04921113685e5ac2005f7b161c09b Mon Sep 17 00:00:00 2001 From: mikereiche Date: Wed, 11 Nov 2020 22:27:51 -0800 Subject: [PATCH 3/3] DATACOUCH-588 - Refactoring part 1 of n. --- pom.xml | 7 + ...ExecutableFindByQueryOperationSupport.java | 6 +- ...ecutableRemoveByQueryOperationSupport.java | 8 +- .../core/ReactiveFindByQueryOperation.java | 8 +- .../ReactiveFindByQueryOperationSupport.java | 2 +- .../core/ReactiveRemoveByIdOperation.java | 1 - ...ReactiveRemoveByQueryOperationSupport.java | 19 +- .../convert/MappingCouchbaseConverter.java | 2 +- .../data/couchbase/core/query/Meta.java | 133 ++++++++++ .../data/couchbase/core/query/Query.java | 16 +- .../{query/BooleanUtil.java => Meta.java} | 41 +-- .../query/AbstractCouchbaseQuery.java | 187 +++---------- .../query/AbstractCouchbaseQueryBase.java | 247 ++++++++++++++++++ .../query/AbstractReactiveCouchbaseQuery.java | 181 ++----------- .../query/CouchbaseQueryExecution.java | 142 +--------- .../query/CouchbaseQueryMethod.java | 44 +++- .../query/PartTreeCouchbaseQuery.java | 14 +- .../ReactiveCouchbaseQueryExecution.java | 77 +----- .../ReactiveCouchbaseRepositoryQuery.java | 5 + .../query/ReactivePartTreeCouchbaseQuery.java | 4 +- .../ReactiveStringBasedCouchbaseQuery.java | 42 ++- .../query/ResultProcessingConverter.java | 90 +++++++ .../query/StringBasedCouchbaseQuery.java | 47 +--- .../query/StringN1qlQueryCreator.java | 33 +-- .../support/CouchbaseRepositoryFactory.java | 12 +- .../ReactiveCouchbaseRepositoryFactory.java | 16 +- .../support/SimpleCouchbaseRepository.java | 2 +- ...ouchbaseTemplateQueryIntegrationTests.java | 12 +- .../couchbase/domain/AirportRepository.java | 1 - .../data/couchbase/domain/Config.java | 25 +- ...chbaseRepositoryQueryIntegrationTests.java | 6 - ...chbaseRepositoryQueryIntegrationTests.java | 4 - .../query/StringN1qlQueryCreatorTests.java | 1 - src/test/resources/integration.properties | 2 +- 34 files changed, 716 insertions(+), 721 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/query/Meta.java rename src/main/java/org/springframework/data/couchbase/repository/{query/BooleanUtil.java => Meta.java} (53%) create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/query/ResultProcessingConverter.java diff --git a/pom.xml b/pom.xml index f6a6789f3..5e1661471 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,13 @@ test + + io.projectreactor + reactor-test + 3.1.0.RELEASE + test + + com.fasterxml.jackson.core jackson-databind 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 8daaf0405..a5c4838c1 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -41,7 +41,8 @@ public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) { @Override public ExecutableFindByQuery findByQuery(final Class domainType) { - return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, "_default._default"); + return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, + "_default._default"); } static class ExecutableFindByQuerySupport implements ExecutableFindByQuery { @@ -58,7 +59,8 @@ static class ExecutableFindByQuerySupport implements ExecutableFindByQuery this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), domainType, query, scanConsistency, collection); + this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), domainType, query, scanConsistency, + collection); this.scanConsistency = scanConsistency; this.collection = collection; } 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 5db441957..32e98a98a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java @@ -34,7 +34,8 @@ public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) @Override public ExecutableRemoveByQuery removeByQuery(Class domainType) { - return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, "_default._default"); + return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, + "_default._default"); } static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuery { @@ -51,8 +52,8 @@ static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuer this.template = template; this.domainType = domainType; this.query = query; - this.reactiveSupport = new ReactiveRemoveByQuerySupport<>(template.reactive(), domainType, query, - scanConsistency); + this.reactiveSupport = new ReactiveRemoveByQuerySupport<>(template.reactive(), domainType, query, scanConsistency, + collection); this.scanConsistency = scanConsistency; this.collection = collection; } @@ -71,6 +72,7 @@ public TerminatingRemoveByQuery matching(final Query query) { public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } + @Override public RemoveByQueryInCollection inCollection(final String collection) { return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); 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 1a134dc6c..1b99af3c7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -113,7 +113,7 @@ interface FindByQueryConsistentWith extends FindByQueryWithQuery { /** * Collection override (optional). */ - interface FindInCollection extends FindByQueryWithQuery { + interface FindByQueryInCollection extends FindByQueryWithQuery { /** * Explicitly set the name of the collection to perform the query on.
@@ -123,13 +123,13 @@ interface FindInCollection extends FindByQueryWithQuery { * @return new instance of {@link FindWithProjection}. * @throws IllegalArgumentException if collection is {@literal null}. */ - FindInCollection inCollection(String collection); + FindByQueryInCollection inCollection(String collection); } /** * Result type override (optional). */ - interface FindWithProjection extends FindInCollection, FindDistinct { + interface FindWithProjection extends FindByQueryInCollection, FindDistinct { /** * Define the target type fields should be mapped to.
@@ -233,6 +233,6 @@ interface TerminatingDistinct extends DistinctWithQuery { Flux all(); } - interface ReactiveFindByQuery extends FindByQueryConsistentWith, FindInCollection, FindDistinct {} + interface ReactiveFindByQuery extends FindByQueryConsistentWith, FindByQueryInCollection, FindDistinct {} } 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 717672791..104c19110 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -80,7 +80,7 @@ public FindByQueryConsistentWith consistentWith(QueryScanConsistency scanCons } @Override - public FindInCollection inCollection(String collection) { + public FindByQueryInCollection inCollection(String collection) { return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency, collection); } 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 4a9897ea8..d1cc5fa65 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java @@ -49,7 +49,6 @@ interface RemoveByIdWithDurability extends RemoveByIdWithCollection, WithDurabil } - interface ReactiveRemoveById extends RemoveByIdWithDurability {} } 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 5b96d1c5c..ae3b89314 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -38,7 +38,8 @@ public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate tem @Override public ReactiveRemoveByQuery removeByQuery(Class domainType) { - return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED, + "_default._default"); } static class ReactiveRemoveByQuerySupport implements ReactiveRemoveByQuery { @@ -47,13 +48,15 @@ static class ReactiveRemoveByQuerySupport implements ReactiveRemoveByQuery private final Class domainType; private final Query query; private final QueryScanConsistency scanConsistency; + private final String collection; ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, final Query query, - final QueryScanConsistency scanConsistency) { + final QueryScanConsistency scanConsistency, String collection) { this.template = template; this.domainType = domainType; this.query = query; this.scanConsistency = scanConsistency; + this.collection = collection; } @Override @@ -69,7 +72,8 @@ public Flux all() { return throwable; } }).flatMapMany(ReactiveQueryResult::rowsAsObject) - .map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS), Optional.empty())); + .map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS), + Optional.empty())); }); } @@ -83,18 +87,19 @@ private QueryOptions buildQueryOptions() { @Override public TerminatingRemoveByQuery matching(final Query query) { - return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } @Override public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { - return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } @Override - public RemoveByQueryConsistentWith inCollection(final String collection) { - return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + public RemoveByQueryInCollection inCollection(final String collection) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, collection); } + private String assembleDeleteQuery() { return query.toN1qlRemoveString(template, this.domainType); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index 9b76c0d1d..e84055707 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -267,7 +267,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { || prop.isAnnotationPresent(N1qlJoin.class)) { return; } - Object obj = prop.isIdProperty() ? source.getId() : getValueInternal(prop, source, instance); + Object obj = prop.isIdProperty() && parent == null ? source.getId() : getValueInternal(prop, source, instance); accessor.setProperty(prop, obj); } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Meta.java b/src/main/java/org/springframework/data/couchbase/core/query/Meta.java new file mode 100644 index 000000000..26d165a0e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/query/Meta.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.query; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Meta-data for {@link Query} instances. + * + * @author Michael Reiche + */ +public class Meta { + + private enum MetaKey { + EXAMPLE("$example"); + + private String key; + + MetaKey(String key) { + this.key = key; + } + } + + private final Map values = new LinkedHashMap<>(2); + + public Meta() {} + + /** + * Copy a {@link Meta} object. + * + * @since 2.2 + * @param source + */ + Meta(Meta source) { + this.values.putAll(source.values); + } + + /** + * @return + */ + public boolean hasValues() { + return !this.values.isEmpty(); + } + + /** + * Get {@link Iterable} of set meta values. + * + * @return + */ + public Iterable> values() { + return Collections.unmodifiableSet(this.values.entrySet()); + } + + /** + * Sets or removes the value in case of {@literal null} or empty {@link String}. + * + * @param key must not be {@literal null} or empty. + * @param value + */ + void setValue(String key, @Nullable Object value) { + + Assert.hasText(key, "Meta key must not be 'null' or blank."); + + if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) { + this.values.remove(key); + } + this.values.put(key, value); + } + + @Nullable + @SuppressWarnings("unchecked") + private T getValue(String key) { + return (T) this.values.get(key); + } + + private T getValue(String key, T defaultValue) { + + T value = getValue(key); + return value != null ? value : defaultValue; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int hash = ObjectUtils.nullSafeHashCode(this.values); + return hash; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof Meta)) { + return false; + } + + Meta other = (Meta) obj; + return ObjectUtils.nullSafeEquals(this.values, other.values); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index afcf8ad7b..1f7a2b6a8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -39,8 +39,6 @@ import com.couchbase.client.java.query.QueryScanConsistency; /** - * Couchbase Query - * * @author Michael Nitschinger * @author Michael Reiche */ @@ -109,22 +107,13 @@ public Query skip(long skip) { * Limit the number of returned documents to {@code limit}. * * @param limit - * @return this + * @return */ public Query limit(int limit) { this.limit = limit; return this; } - /** - * limit - * - * @return limit - */ - public int getLimit() { - return limit; - } - /** * Sets the given pagination information on the {@link Query} instance. Will transparently set {@code skip} and * {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}. @@ -330,4 +319,7 @@ public QueryOptions buildQueryOptions(QueryScanConsistency scanConsistency) { return options; } + public void setMeta(Meta metaAnnotation) { + Meta meta = metaAnnotation; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java b/src/main/java/org/springframework/data/couchbase/repository/Meta.java similarity index 53% rename from src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java rename to src/main/java/org/springframework/data/couchbase/repository/Meta.java index 90ee69e3e..298379ee9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/BooleanUtil.java +++ b/src/main/java/org/springframework/data/couchbase/repository/Meta.java @@ -13,37 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.query; +package org.springframework.data.couchbase.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.QueryAnnotation; /** - * Utility class containing methods to interact with boolean values. - * * @author Michael Reiche * @since 4.1 */ -final class BooleanUtil { - - private BooleanUtil() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - /** - * Count the number of {@literal true} values. - * - * @param values - * @return the number of values that are {@literal true}. - */ - static int countBooleanTrueValues(boolean... values) { - - int count = 0; - - for (boolean value : values) { - - if (value) { - count++; - } - } +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@QueryAnnotation +public @interface Meta { - return count; - } } 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 995dd70e9..a56c59ffc 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,23 +15,18 @@ */ package org.springframework.data.couchbase.repository.query; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.reactivestreams.Publisher; 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.query.Query; -import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution.DeleteExecution; +import org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution.PagedExecution; import org.springframework.data.repository.core.EntityMetadata; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.util.ClassTypeInformation; -import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -42,15 +37,10 @@ * @author Michael Reiche * @since 4.1 */ -public abstract class AbstractCouchbaseQuery implements RepositoryQuery { +public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase + implements RepositoryQuery { - private final CouchbaseQueryMethod method; - private final CouchbaseOperations operations; - private final EntityInstantiators instantiators; - // private final FindWithProjection findOperationWithProjection; // TODO private final ExecutableFindByQueryOperation.ExecutableFindByQuery findOperationWithProjection; - private final SpelExpressionParser expressionParser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link AbstractCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and @@ -63,71 +53,17 @@ public abstract class AbstractCouchbaseQuery implements RepositoryQuery { */ public AbstractCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations operations, SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - + super(method, operations, expressionParser, evaluationContextProvider); Assert.notNull(method, "CouchbaseQueryMethod must not be null!"); Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!"); Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!"); - - this.method = method; - this.operations = operations; - this.instantiators = new EntityInstantiators(); - this.expressionParser = expressionParser; - this.evaluationContextProvider = evaluationContextProvider; - + // this.operations = operations; EntityMetadata metadata = method.getEntityInformation(); Class type = metadata.getJavaType(); - this.findOperationWithProjection = operations.findByQuery(type); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() - */ - public CouchbaseQueryMethod getQueryMethod() { - return method; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) - */ - public Object execute(Object[] parameters) { - - return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) - : execute(new ReactiveCouchbaseParameterAccessor(method, parameters)); - } - - @SuppressWarnings("unchecked") - private Object executeDeferred(Object[] parameters) { - - ReactiveCouchbaseParameterAccessor parameterAccessor = new ReactiveCouchbaseParameterAccessor(method, parameters); - - if (getQueryMethod().isCollectionQuery()) { - return Flux.defer(() -> (Publisher) execute(parameterAccessor)); - } - - return Mono.defer(() -> (Mono) execute(parameterAccessor)); - } - - private Object execute(ParametersParameterAccessor parameterAccessor) { - - // ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(), - // parameterAccessor); - - TypeInformation returnType = ClassTypeInformation - .from(method.getResultProcessor().getReturnedType().getReturnedType()); - ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); - Class typeToRead = processor.getReturnedType().getTypeToRead(); - - if (typeToRead == null && returnType.getComponentType() != null) { - typeToRead = returnType.getComponentType().getType(); - } - - return doExecute(method, processor, parameterAccessor, typeToRead); - } - /** * Execute the {@link RepositoryQuery} of the given method with the parameters provided by the * {@link ParametersParameterAccessor accessor} @@ -137,22 +73,23 @@ private Object execute(ParametersParameterAccessor parameterAccessor) { * @param accessor for providing invocation arguments. Never {@literal null}. * @param typeToRead the desired component target type. Can be {@literal null}. */ + @Override protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processor, ParametersParameterAccessor accessor, @Nullable Class typeToRead) { Query query = createQuery(accessor); query = applyAnnotatedConsistencyIfPresent(query); - // query = applyAnnotatedCollationIfPresent(query, accessor); // TODO + // query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented - ExecutableFindByQueryOperation.ExecutableFindByQuery find = typeToRead == null // + ExecutableFindByQueryOperation.ExecutableFindByQuery find = typeToRead == null ? findOperationWithProjection // - : findOperationWithProjection; // TODO .as(typeToRead); + : findOperationWithProjection; // not yet implemented in core .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // TODO + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented CouchbaseQueryExecution execution = getExecution(accessor, - new CouchbaseQueryExecution.ResultProcessingConverter(processor, getOperations(), instantiators), find); + new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find); return execution.execute(query, processor.getReturnedType().getDomainType(), collection); } @@ -169,111 +106,51 @@ private CouchbaseQueryExecution getExecution(ParameterAccessor accessor, Convert resultProcessing); } + /** + * Returns the execution to wrap + * + * @param accessor must not be {@literal null}. + * @param operation must not be {@literal null}. + * @return + */ private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, ExecutableFindByQueryOperation.ExecutableFindByQuery operation) { if (isDeleteQuery()) { - return new CouchbaseQueryExecution.DeleteExecution(getOperations(), method); - /* // TODO - } else if (isTailable(method)) { - return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); - */ - } else if (method.isCollectionQuery()) { + return new DeleteExecution(getOperations(), getQueryMethod()); + } else if (isTailable(getQueryMethod())) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all() + } else if (getQueryMethod().isCollectionQuery()) { return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); } else if (isCountQuery()) { return (q, t, c) -> operation.matching(q).count(); } else if (isExistsQuery()) { return (q, t, c) -> operation.matching(q).exists(); - } else if (method.isPageQuery()) { - return new CouchbaseQueryExecution.PagedExecution(operation, accessor.getPageable()); + } else if (getQueryMethod().isPageQuery()) { + return new PagedExecution(operation, accessor.getPageable()); } else { return (q, t, c) -> { - ExecutableFindByQueryOperation.TerminatingFindByQuery find = operation.matching(q); - if (isCountQuery()) { return find.count(); } - return isLimiting() ? find.first() : find.one(); }; } } - private boolean isTailable(CouchbaseQueryMethod method) { - return false; // method.getTailableAnnotation() != null; // TODO - } - /** - * Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given - * {@link Query} if present. + * Apply Meta annotation to query * - * @param query the {@link Query} to potentially apply the sort to. - * @return the query with potential scan consistency applied. - * @since 4.1 + * @param query must not be {@literal null}. + * @return Query */ - Query applyAnnotatedConsistencyIfPresent(Query query) { + Query applyQueryMetaAttributesWhenPresent(Query query) { - if (!method.hasScanConsistencyAnnotation()) { - return query; + if (getQueryMethod().hasQueryMetaAttributes()) { + query.setMeta(getQueryMethod().getQueryMetaAttributes()); } - return query.scanConsistency(method.getScanConsistencyAnnotation().query()); - } - - /** - * Creates a {@link Query} instance using the given {@link ParametersParameterAccessor}. Will delegate to - * {@link #createQuery(ParametersParameterAccessor)} by default but allows customization of the count query to be - * triggered. - * - * @param accessor must not be {@literal null}. - * @return - */ - protected Query createCountQuery(ParametersParameterAccessor accessor) { - return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); - } - - /** - * Creates a {@link Query} instance using the given {@link ParameterAccessor} - * - * @param accessor must not be {@literal null}. - * @return - */ - protected abstract Query createQuery(ParametersParameterAccessor accessor); - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isCountQuery() - */ - protected boolean isCountQuery() { - return getQueryMethod().isCountQuery(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isExistsQuery() - */ - - protected boolean isExistsQuery() { - return getQueryMethod().isExistsQuery(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isDeleteQuery() - */ - protected boolean isDeleteQuery() { - return getQueryMethod().isDeleteQuery(); - } - - /** - * Return whether the query has an explicit limit set. - * - * @return - * @since 2.0.4 - */ - protected abstract boolean isLimiting(); - public CouchbaseOperations getOperations() { - return operations; + return query; } } 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 new file mode 100644 index 000000000..a6aec181d --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractCouchbaseQueryBase.java @@ -0,0 +1,247 @@ +/* + * Copyright 2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.repository.query; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.reactivestreams.Publisher; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.repository.core.EntityMetadata; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * {@link RepositoryQuery} implementation for Couchbase. + * + * CouchbaseOperationsType is either CouchbaseOperations or ReactiveCouchbaseOperations + * @author Michael Reiche + * @since 4.1 + */ +public abstract class AbstractCouchbaseQueryBase implements RepositoryQuery { + + private final CouchbaseQueryMethod method; + private final CouchbaseOperationsType operations; + private final EntityInstantiators instantiators; + private final ExecutableFindByQueryOperation.ExecutableFindByQuery findOperationWithProjection; + private final SpelExpressionParser expressionParser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + + /** + * Creates a new {@link AbstractCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and + * {@link org.springframework.data.couchbase.core.CouchbaseOperations}. + * + * @param method must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param expressionParser must not be {@literal null}. + * @param evaluationContextProvider must not be {@literal null}. + */ + public AbstractCouchbaseQueryBase(CouchbaseQueryMethod method, CouchbaseOperationsType operations, + SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + + Assert.notNull(method, "CouchbaseQueryMethod must not be null!"); + Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!"); + Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); + Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!"); + + this.method = method; + this.operations = operations; + this.instantiators = new EntityInstantiators(); + this.expressionParser = expressionParser; + this.evaluationContextProvider = evaluationContextProvider; + + EntityMetadata metadata = method.getEntityInformation(); + Class type = metadata.getJavaType(); + this.findOperationWithProjection = operations instanceof CouchbaseOperations + ? ((CouchbaseOperations) operations).findByQuery(type) + : null; // not yet implemented for Reactive + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() + */ + public CouchbaseQueryMethod getQueryMethod() { + return method; + }; + + /* + * (non-Javadoc) + */ + public CouchbaseOperationsType getOperations() { + return operations; + } + + /* + * (non-Javadoc) + */ + EntityInstantiators getInstantiators() { + return instantiators; + } + + /** + * Execute the query with the provided parameters + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) + */ + public Object execute(Object[] parameters) { + return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) + : execute(new ReactiveCouchbaseParameterAccessor(getQueryMethod(), parameters)); + } + + private Object executeDeferred(Object[] parameters) { + ReactiveCouchbaseParameterAccessor parameterAccessor = new ReactiveCouchbaseParameterAccessor(method, parameters); + if (getQueryMethod().isCollectionQuery()) { + return Flux.defer(() -> (Publisher) execute(parameterAccessor)); + } + return Mono.defer(() -> (Mono) execute(parameterAccessor)); + } + + private Object execute(ParametersParameterAccessor parameterAccessor) { + TypeInformation returnType = ClassTypeInformation + .from(method.getResultProcessor().getReturnedType().getReturnedType()); + ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); + Class typeToRead = processor.getReturnedType().getTypeToRead(); + + if (typeToRead == null && returnType.getComponentType() != null) { + typeToRead = returnType.getComponentType().getType(); + } + return doExecute(getQueryMethod(), processor, parameterAccessor, typeToRead); + } + + /** + * Execute the {@link RepositoryQuery} of the given method with the parameters provided by the + * {@link ParametersParameterAccessor accessor} + * + * @param method the {@link ReactiveCouchbaseQueryMethod} invoked. Never {@literal null}. + * @param processor {@link ResultProcessor} for post procession. Never {@literal null}. + * @param accessor for providing invocation arguments. Never {@literal null}. + * @param typeToRead the desired component target type. Can be {@literal null}. + */ + abstract protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processor, + ParametersParameterAccessor accessor, @Nullable Class typeToRead); + + /** + * Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given + * {@link Query} if present. + * + * @param query the {@link Query} to potentially apply the sort to. + * @return the query with potential scan consistency applied. + * @since 4.1 + */ + Query applyAnnotatedConsistencyIfPresent(Query query) { + if (!method.hasScanConsistencyAnnotation()) { + return query; + } + return query.scanConsistency(method.getScanConsistencyAnnotation().query()); + } + + /** + * Creates a {@link Query} instance using the given {@link ParametersParameterAccessor}. Will delegate to + * {@link #createQuery(ParametersParameterAccessor)} by default but allows customization of the count query to be + * triggered. + * + * @param accessor must not be {@literal null}. + * @return + */ + protected abstract Query createCountQuery(ParametersParameterAccessor accessor); + + /** + * Creates a {@link Query} instance using the given {@link ParameterAccessor} + * + * @param accessor must not be {@literal null}. + * @return + */ + protected abstract Query createQuery(ParametersParameterAccessor accessor); + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isCountQuery() + */ + protected boolean isCountQuery() { + return getQueryMethod().isCountQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isExistsQuery() + */ + protected boolean isExistsQuery() { + return getQueryMethod().isExistsQuery(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isDeleteQuery() + */ + protected boolean isDeleteQuery() { + return getQueryMethod().isDeleteQuery(); + } + + /** + * Return whether the query is tailable + * + * @return + */ + boolean isTailable(CouchbaseQueryMethod method) { + return false; // method.getTailableAnnotation() != null; // Not yet implemented + } + + /** + * Return whether the query has an explicit limit set. + * + * @return + */ + protected abstract boolean isLimiting(); + + /** + * Return whether there are ambiguous projection flags + * + * @return + */ + static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) { + return multipleOf(isCountQuery, isExistsQuery, isDeleteQuery); + } + + /** + * Count the number of {@literal true} values. + * + * @param values + * @return are there more than one of these true? + */ + static boolean multipleOf(boolean... values) { + int count = 0; + for (boolean value : values) { + if (value) { + if (count != 0) { + return true; + } + count++; + } + } + return false; + } +} 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 fff5efa97..c704ce0d8 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,27 +15,19 @@ */ package org.springframework.data.couchbase.repository.query; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation; import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation.ReactiveFindByQuery; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.DeleteExecution; -import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.ResultProcessingConverter; import org.springframework.data.couchbase.repository.query.ReactiveCouchbaseQueryExecution.ResultProcessingExecution; -import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.repository.core.EntityMetadata; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.util.ClassTypeInformation; -import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -46,15 +38,10 @@ * @author Michael Reiche * @since 4.1 */ -public abstract class AbstractReactiveCouchbaseQuery implements RepositoryQuery { +public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQueryBase + implements RepositoryQuery { - private final ReactiveCouchbaseQueryMethod method; - private final ReactiveCouchbaseOperations operations; - private final EntityInstantiators instantiators; - // private final FindWithProjection findOperationWithProjection; // TODO private final ReactiveFindByQuery findOperationWithProjection; - private final SpelExpressionParser expressionParser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link AbstractReactiveCouchbaseQuery} from the given {@link ReactiveCouchbaseQueryMethod} and @@ -67,71 +54,17 @@ public abstract class AbstractReactiveCouchbaseQuery implements RepositoryQuery */ public AbstractReactiveCouchbaseQuery(ReactiveCouchbaseQueryMethod method, ReactiveCouchbaseOperations operations, SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - + super(method, operations, expressionParser, evaluationContextProvider); Assert.notNull(method, "CouchbaseQueryMethod must not be null!"); Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!"); Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!"); - this.method = method; - this.operations = operations; - this.instantiators = new EntityInstantiators(); - this.expressionParser = expressionParser; - this.evaluationContextProvider = evaluationContextProvider; - EntityMetadata metadata = method.getEntityInformation(); Class type = metadata.getJavaType(); - this.findOperationWithProjection = operations.findByQuery(type); } - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() - */ - public ReactiveCouchbaseQueryMethod getQueryMethod() { - return method; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) - */ - public Object execute(Object[] parameters) { - - return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) - : execute(new ReactiveCouchbaseParameterAccessor(method, parameters)); - } - - @SuppressWarnings("unchecked") - private Object executeDeferred(Object[] parameters) { - - ReactiveCouchbaseParameterAccessor parameterAccessor = new ReactiveCouchbaseParameterAccessor(method, parameters); - - if (getQueryMethod().isCollectionQuery()) { - return Flux.defer(() -> (Publisher) execute(parameterAccessor)); - } - - return Mono.defer(() -> (Mono) execute(parameterAccessor)); - } - - private Object execute(ReactiveCouchbaseParameterAccessor parameterAccessor) { - - // ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(), - // parameterAccessor); - - TypeInformation returnType = ClassTypeInformation - .from(method.getResultProcessor().getReturnedType().getReturnedType()); - ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor); - Class typeToRead = processor.getReturnedType().getTypeToRead(); - - if (typeToRead == null && returnType.getComponentType() != null) { - typeToRead = returnType.getComponentType().getType(); - } - - return doExecute(method, processor, parameterAccessor, typeToRead); - } - /** * Execute the {@link RepositoryQuery} of the given method with the parameters provided by the * {@link ParametersParameterAccessor accessor} @@ -141,22 +74,21 @@ private Object execute(ReactiveCouchbaseParameterAccessor parameterAccessor) { * @param accessor for providing invocation arguments. Never {@literal null}. * @param typeToRead the desired component target type. Can be {@literal null}. */ - protected Object doExecute(ReactiveCouchbaseQueryMethod method, ResultProcessor processor, + protected Object doExecute(CouchbaseQueryMethod method, ResultProcessor processor, ParametersParameterAccessor accessor, @Nullable Class typeToRead) { Query query = createQuery(accessor); - query = applyAnnotatedConsistencyIfPresent(query); - // query = applyAnnotatedCollationIfPresent(query, accessor); // TODO + // query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented ReactiveFindByQueryOperation.FindByQueryWithQuery find = typeToRead == null // ? findOperationWithProjection // - : findOperationWithProjection; // TODO .as(typeToRead); + : findOperationWithProjection; // note yet implemented in core .as(typeToRead); - String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // TODO + String collection = "_default._default";// method.getEntityInformation().getCollectionName(); // not yet implemented ReactiveCouchbaseQueryExecution execution = getExecution(accessor, - new ResultProcessingConverter(processor, getOperations(), instantiators), find); + new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find); return execution.execute(query, processor.getReturnedType().getDomainType(), collection); } @@ -169,21 +101,24 @@ protected Object doExecute(ReactiveCouchbaseQueryMethod method, ResultProcessor */ private ReactiveCouchbaseQueryExecution getExecution(ParameterAccessor accessor, Converter resultProcessing, ReactiveFindByQueryOperation.FindByQueryWithQuery operation) { - return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), - resultProcessing); + return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing); } + /** + * Returns the execution to wrap + * + * @param accessor must not be {@literal null}. + * @param operation must not be {@literal null}. + * @return + */ private ReactiveCouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, ReactiveFindByQueryOperation.FindByQueryWithQuery operation) { if (isDeleteQuery()) { - return new DeleteExecution(getOperations(), method); - /* TODO - } else if (isTailable(method)) { - return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail(); - this.metadata.getReturnType(this.method).getType() - */ - } else if (method.isCollectionQuery() /*|| method.getReturnedObjectType() instanceof Flux)*/) { + return new DeleteExecution(getOperations(), getQueryMethod()); + } else if (isTailable(getQueryMethod())) { + return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all() + } else if (getQueryMethod().isCollectionQuery()) { return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); } else if (isCountQuery()) { return (q, t, c) -> operation.matching(q).count(); @@ -197,80 +132,18 @@ private ReactiveCouchbaseQueryExecution getExecutionToWrap(ParameterAccessor acc } } - private boolean isTailable(ReactiveCouchbaseQueryMethod method) { - return false; // method.getTailableAnnotation() != null; // TODO - } - /** - * Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given - * {@link Query} if present. + * Apply Meta annotation to query * - * @param query the {@link Query} to potentially apply the sort to. - * @return the query with potential scan consistency applied. - * @since 4.1 + * @param query must not be {@literal null}. + * @return Query */ - Query applyAnnotatedConsistencyIfPresent(Query query) { + Query applyQueryMetaAttributesWhenPresent(Query query) { - if (!method.hasScanConsistencyAnnotation()) { - return query; + if (getQueryMethod().hasQueryMetaAttributes()) { + query.setMeta(getQueryMethod().getQueryMetaAttributes()); } - return query.scanConsistency(method.getScanConsistencyAnnotation().query()); - } - - /** - * Creates a {@link Query} instance using the given {@link ParametersParameterAccessor}. Will delegate to - * {@link #createQuery(ParametersParameterAccessor)} by default but allows customization of the count query to be - * triggered. - * - * @param accessor must not be {@literal null}. - * @return - */ - protected Query createCountQuery(ParametersParameterAccessor accessor) { - return /*applyQueryMetaAttributesWhenPresent*/(createQuery(accessor)); // TODO - } - - /** - * Creates a {@link Query} instance using the given {@link ParameterAccessor} - * - * @param accessor must not be {@literal null}. - * @return - */ - protected abstract Query createQuery(ParametersParameterAccessor accessor); - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isCountQuery() - */ - protected boolean isCountQuery() { - return getQueryMethod().isCountQuery(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isExistsQuery() - */ - - protected boolean isExistsQuery() { - return getQueryMethod().isExistsQuery(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isDeleteQuery() - */ - protected boolean isDeleteQuery() { - return getQueryMethod().isDeleteQuery(); - } - - /** - * Return whether the query has an explicit limit set. - * - * @return - * @since 2.0.4 - */ - protected abstract boolean isLimiting(); - public ReactiveCouchbaseOperations getOperations() { - return operations; + return query; } } 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 95ac6abb4..c64054b24 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 @@ -15,12 +15,8 @@ */ package org.springframework.data.couchbase.repository.query; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - import java.util.List; -import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation; @@ -28,17 +24,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; -import org.springframework.data.mapping.model.EntityInstantiators; -import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.repository.query.ReturnedType; +import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.support.PageableExecutionUtils; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a * {@link org.springframework.data.repository.query.QueryMethod} a {@link Query} can be executed in various flavors. - * TODO: seems to be a lot of duplication with ReactiveCouchbaseQueryExecution * * @author Michael Reiche * @since 4.1 @@ -50,15 +42,14 @@ interface CouchbaseQueryExecution { /** * {@link CouchbaseQueryExecution} removing documents matching the query. - * */ final class DeleteExecution implements CouchbaseQueryExecution { private final CouchbaseOperations operations; - private final CouchbaseQueryMethod method; + private final QueryMethod method; - public DeleteExecution(CouchbaseOperations operations, CouchbaseQueryMethod method) { + public DeleteExecution(CouchbaseOperations operations, QueryMethod method) { this.operations = operations; this.method = method; } @@ -67,10 +58,9 @@ public DeleteExecution(CouchbaseOperations operations, CouchbaseQueryMethod meth * (non-Javadoc) * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery.Execution#execute(org.springframework.data.couchbase.core.query.Query, java.lang.Class, java.lang.String) */ - @Override public Object execute(Query query, Class type, String collection) { - return operations.removeByQuery(type).inCollection(collection).matching(query).all(); + return operations.removeByQuery(type).matching(query).all(); } } @@ -85,10 +75,8 @@ final class ResultProcessingExecution implements CouchbaseQueryExecution { private final Converter converter; public ResultProcessingExecution(CouchbaseQueryExecution delegate, Converter converter) { - Assert.notNull(delegate, "Delegate must not be null!"); Assert.notNull(converter, "Converter must not be null!"); - this.delegate = delegate; this.converter = converter; } @@ -99,70 +87,8 @@ public Object execute(Query query, Class type, String collection) { } } - /** - * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. - * - */ - final class ResultProcessingConverter implements Converter { - - private final ResultProcessor processor; - private final CouchbaseOperations operations; - private final EntityInstantiators instantiators; - - public ResultProcessingConverter(ResultProcessor processor, CouchbaseOperations operations, - EntityInstantiators instantiators) { - - Assert.notNull(processor, "Processor must not be null!"); - Assert.notNull(operations, "Operations must not be null!"); - Assert.notNull(instantiators, "Instantiators must not be null!"); - - this.processor = processor; - this.operations = operations; - this.instantiators = instantiators; - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ - @Override - public Object convert(Object source) { - - ReturnedType returnedType = processor.getReturnedType(); - - if (isVoid(returnedType)) { - - if (source instanceof Mono) { - return ((Mono) source).then(); - } - - if (source instanceof Publisher) { - return Flux.from((Publisher) source).then(); - } - } - - if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { - return source; - } - - if (!operations.getConverter().getMappingContext().hasPersistentEntityFor(returnedType.getReturnedType())) { - return source; - } - - Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), - operations.getConverter().getMappingContext(), instantiators); - - return processor.processResult(source, converter); - } - } - - static boolean isVoid(ReturnedType returnedType) { - return returnedType.getReturnedType().equals(Void.class); - } - /** * {@link CouchbaseQueryExecution} for {@link Slice} query methods. - * */ final class SlicedExecution implements CouchbaseQueryExecution { @@ -170,10 +96,8 @@ final class SlicedExecution implements CouchbaseQueryExecution { private final Pageable pageable; public SlicedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery find, Pageable pageable) { - Assert.notNull(find, "Find must not be null!"); Assert.notNull(pageable, "Pageable must not be null!"); - this.find = find; this.pageable = pageable; } @@ -185,22 +109,17 @@ public SlicedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery find @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object execute(Query query, Class type, String collection) { - int pageSize = pageable.getPageSize(); - // Apply Pageable but tweak limit to peek into next page Query modifiedQuery = query.with(pageable).limit(pageSize + 1); List result = find.matching(modifiedQuery).all(); - boolean hasNext = result.size() > pageSize; - return new SliceImpl(hasNext ? result.subList(0, pageSize) : result, pageable, hasNext); } } /** * {@link CouchbaseQueryExecution} for pagination queries. - * */ final class PagedExecution implements CouchbaseQueryExecution { @@ -208,10 +127,8 @@ final class PagedExecution implements CouchbaseQueryExecution { private final Pageable pageable; public PagedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery operation, Pageable pageable) { - Assert.notNull(operation, "Operation must not be null!"); Assert.notNull(pageable, "Pageable must not be null!"); - this.operation = operation; this.pageable = pageable; } @@ -222,68 +139,19 @@ public PagedExecution(ExecutableFindByQueryOperation.ExecutableFindByQuery op */ @Override public Object execute(Query query, Class type, String collection) { - - int overallLimit = query.getLimit(); - + int overallLimit = 0; // query.getLimit(); ExecutableFindByQueryOperation.TerminatingFindByQuery matching = operation.matching(query); - // Apply raw pagination query.with(pageable); - // Adjust limit if page would exceed the overall limit if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) { query.limit((int) (overallLimit - pageable.getOffset())); } - return PageableExecutionUtils.getPage(matching.all(), pageable, () -> { - long count = operation.matching(query.skip(-1).limit(-1)).count(); return overallLimit != 0 ? Math.min(count, overallLimit) : count; }); } } - /* - final class ReactivePagedExecution implements CouchbaseQueryExecution { // TODO ?? - - private final ReactiveFindByQueryOperation.ReactiveFindByQuery operation; - private final Pageable pageable; - - public ReactivePagedExecution(ReactiveFindByQueryOperation.ReactiveFindByQuery operation, Pageable pageable) { - - Assert.notNull(operation, "Operation must not be null!"); - Assert.notNull(pageable, "Pageable must not be null!"); - - this.operation = operation; - this.pageable = pageable; - } - - - /xx* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.CouchbaseQueryExecution#execute(org.springframework.data.couchbase.core.query.Query) - *xx/ - @Override - public Object execute(Query query) { - - int overallLimit = query.getLimit(); - - ReactiveFindByQueryOperation.TerminatingFindByQuery matching = operation.matching(query); - - // Apply raw pagination - query.with(pageable); - - // Adjust limit if page would exceed the overall limit - if (overallLimit != 0 && pageable.getOffset() + pageable.getPageSize() > overallLimit) { - query.limit((int) (overallLimit - pageable.getOffset())); - } - - return PageableExecutionUtils.getPage(matching.all().collectList().block(), pageable, () -> { - - long count = operation.matching(query.skip(-1).limit(-1)).count().block(); - return overallLimit != 0 ? Math.min(count, overallLimit) : count; - }); - } - } - */ } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java index a14195a96..862434737 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java @@ -17,6 +17,7 @@ package org.springframework.data.couchbase.repository.query; import java.lang.reflect.Method; +import java.util.Locale; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; @@ -24,6 +25,7 @@ import org.springframework.data.couchbase.core.query.Dimensional; import org.springframework.data.couchbase.core.query.View; import org.springframework.data.couchbase.core.query.WithConsistency; +import org.springframework.data.couchbase.repository.Meta; import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.data.mapping.context.MappingContext; @@ -32,8 +34,8 @@ import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.util.ReactiveWrapperConverters; -import org.springframework.data.repository.util.ReactiveWrappers; -import org.springframework.data.util.Lazy; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -177,6 +179,38 @@ public ScanConsistency getScanConsistencyAnnotation() { return method.getAnnotation(ScanConsistency.class); } + /** + * @return return true if {@link Meta} annotation is available. + */ + public boolean hasQueryMetaAttributes() { + return getMetaAnnotation() != null; + } + + /** + * @return return {@link Meta} annotation + */ + private Meta getMetaAnnotation() { + return method.getAnnotation(Meta.class); + } + + /** + * Returns the {@link org.springframework.data.couchbase.core.query.Meta} attributes to be applied. + * + * @return never {@literal null}. + */ + @Nullable + public org.springframework.data.couchbase.core.query.Meta getQueryMetaAttributes() { + + Meta meta = getMetaAnnotation(); + if (meta == null) { + return new org.springframework.data.couchbase.core.query.Meta(); + } + + org.springframework.data.couchbase.core.query.Meta metaAttributes = new org.springframework.data.couchbase.core.query.Meta(); + + return metaAttributes; + } + /** * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found * nor the attribute was specified. @@ -194,7 +228,7 @@ public String getInlineN1qlQuery() { * @return is this a 'delete'? */ public boolean isDeleteQuery() { - return getName().toLowerCase().startsWith("delete"); + return getName().toLowerCase(Locale.ROOT).startsWith("delete"); } /** @@ -203,7 +237,7 @@ public boolean isDeleteQuery() { * @return is this an 'exists' query? */ public boolean isExistsQuery() { - return getName().toLowerCase().startsWith("exists"); + return getName().toLowerCase(Locale.ROOT).startsWith("exists"); } /** @@ -213,7 +247,7 @@ public boolean isExistsQuery() { * all(). */ public boolean isCountQuery() { - return getName().toLowerCase().startsWith("count"); + return getName().toLowerCase(Locale.ROOT).startsWith("count"); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java index 37bb5346b..006a4cfa1 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeCouchbaseQuery.java @@ -18,9 +18,7 @@ import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; @@ -30,7 +28,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; /** - * {@link RepositoryQuery} implementation for Couchbase. + * {@link RepositoryQuery} implementation for Couchbase. Replaces PartTreeN1qlBasedQuery * * @author Michael Reiche * @since 4.1 @@ -38,9 +36,6 @@ public class PartTreeCouchbaseQuery extends AbstractCouchbaseQuery { private final PartTree tree; - // private final boolean isGeoNearQuery; - private final MappingContext context; - private final ResultProcessor processor; private final CouchbaseConverter converter; /** @@ -56,11 +51,8 @@ public PartTreeCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations o super(method, operations, expressionParser, evaluationContextProvider); - this.processor = method.getResultProcessor(); - for (PartTree.OrPart parts : this.tree = new PartTree(method.getName(), - processor.getReturnedType().getDomainType())) {} - - this.context = operations.getConverter().getMappingContext(); + ResultProcessor processor = method.getResultProcessor(); + this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()); this.converter = operations.getConverter(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java index 02281e0b0..953c25db7 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseQueryExecution.java @@ -15,18 +15,10 @@ */ package org.springframework.data.couchbase.repository.query; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.mapping.model.EntityInstantiators; -import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.repository.query.ReturnedType; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a @@ -42,9 +34,6 @@ interface ReactiveCouchbaseQueryExecution { /** * {@link ReactiveCouchbaseQueryExecution} removing documents matching the query. - * - * @author Mark Paluch - * @author Artyom Gabeev */ final class DeleteExecution implements ReactiveCouchbaseQueryExecution { @@ -61,10 +50,9 @@ public DeleteExecution(ReactiveCouchbaseOperations operations, CouchbaseQueryMet * (non-Javadoc) * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery.Execution#execute(org.springframework.data.couchbase.core.query.Query, java.lang.Class, java.lang.String) */ - @Override public Object execute(Query query, Class type, String collection) { - return operations.removeByQuery(type).inCollection(collection).matching(query).all(); + return operations.removeByQuery(type)/*.inCollection(collection)*/.matching(query).all(); } } @@ -79,10 +67,8 @@ final class ResultProcessingExecution implements ReactiveCouchbaseQueryExecution private final Converter converter; public ResultProcessingExecution(ReactiveCouchbaseQueryExecution delegate, Converter converter) { - Assert.notNull(delegate, "Delegate must not be null!"); Assert.notNull(converter, "Converter must not be null!"); - this.delegate = delegate; this.converter = converter; } @@ -93,65 +79,4 @@ public Object execute(Query query, Class type, String collection) { } } - /** - * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. - * - * @author Mark Paluch - */ - final class ResultProcessingConverter implements Converter { - - private final ResultProcessor processor; - private final ReactiveCouchbaseOperations operations; - private final EntityInstantiators instantiators; - - public ResultProcessingConverter(ResultProcessor processor, ReactiveCouchbaseOperations operations, - EntityInstantiators instantiators) { - - Assert.notNull(processor, "Processor must not be null!"); - Assert.notNull(operations, "Operations must not be null!"); - Assert.notNull(instantiators, "Instantiators must not be null!"); - - this.processor = processor; - this.operations = operations; - this.instantiators = instantiators; - } - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ - @Override - public Object convert(Object source) { - - ReturnedType returnedType = processor.getReturnedType(); - - if (isVoid(returnedType)) { - - if (source instanceof Mono) { - return ((Mono) source).then(); - } - - if (source instanceof Publisher) { - return Flux.from((Publisher) source).then(); - } - } - - if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { - return source; - } - - if (!operations.getConverter().getMappingContext().hasPersistentEntityFor(returnedType.getReturnedType())) { - return source; - } - - Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), - operations.getConverter().getMappingContext(), instantiators); - - return processor.processResult(source, converter); - } - } - - static boolean isVoid(ReturnedType returnedType) { - return returnedType.getReturnedType().equals(Void.class); - } } 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 85f8b0641..5e87ab56e 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 @@ -52,6 +52,11 @@ public Object execute(final Object[] parameters) { .execute(parameters); } + @Override + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return null; + } + @Override protected Query createQuery(ParametersParameterAccessor accessor) { return null; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java index 22183841d..fd60ad402 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeCouchbaseQuery.java @@ -29,10 +29,10 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; /** - * Reactive PartTree {@link RepositoryQuery} implementation for Couchbase. + * Reactive PartTree {@link RepositoryQuery} implementation for Couchbase. Replaces ReactivePartN1qlBasedQuery * * @author Michael Reiche - * @since 4.1 + * @since 4.1 */ public class ReactivePartTreeCouchbaseQuery extends AbstractReactiveCouchbaseQuery { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java index 08170611a..9626e4644 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringBasedCouchbaseQuery.java @@ -17,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.repository.core.NamedQueries; @@ -36,40 +35,33 @@ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbase private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedCouchbaseQuery.class); - private final boolean isCountQuery; - private final boolean isExistsQuery; - private final boolean isDeleteQuery; - private final NamedQueries namedQueries; private final SpelExpressionParser expressionParser; private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final NamedQueries namedQueries; /** * Creates a new {@link ReactiveStringBasedCouchbaseQuery} for the given {@link String}, {@link CouchbaseQueryMethod}, - * {@link CouchbaseOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}. + * {@link ReactiveCouchbaseOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}. * * @param method must not be {@literal null}. - * @param operations must not be {@literal null}. + * @param couchbaseOperations must not be {@literal null}. * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. - * @param namedQueries + * @param namedQueries must not be {@literal null}. */ - public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method, ReactiveCouchbaseOperations operations, - SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider, - NamedQueries namedQueries) { + public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method, + ReactiveCouchbaseOperations couchbaseOperations, SpelExpressionParser expressionParser, + QueryMethodEvaluationContextProvider evaluationContextProvider, NamedQueries namedQueries) { - super(method, operations, expressionParser, evaluationContextProvider); + super(method, couchbaseOperations, expressionParser, evaluationContextProvider); Assert.notNull(expressionParser, "SpelExpressionParser must not be null!"); this.expressionParser = expressionParser; this.evaluationContextProvider = evaluationContextProvider; - this.isCountQuery = method.isCountQuery(); - this.isExistsQuery = method.isExistsQuery(); - this.isDeleteQuery = method.isDeleteQuery(); - - if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + if (hasAmbiguousProjectionFlags(isCountQuery(), isExistsQuery(), isDeleteQuery())) { throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); } @@ -84,32 +76,30 @@ public ReactiveStringBasedCouchbaseQuery(ReactiveCouchbaseQueryMethod method, Re @Override protected Query createQuery(ParametersParameterAccessor accessor) { - // StringN1qlQueryCreator creator = new StringN1qlQueryCreator( accessor, getQueryMethod()); StringN1qlQueryCreator creator = new StringN1qlQueryCreator(accessor, getQueryMethod(), getOperations().getConverter(), getOperations().getBucketName(), expressionParser, evaluationContextProvider, namedQueries); Query query = creator.createQuery(); if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Created query %s for * fields.", query.export() /*, query.getFieldsObject()*/)); + LOG.debug("Created query " + query.export()); } return query; } + @Override + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return applyQueryMetaAttributesWhenPresent(createQuery(accessor)); + } + /* * (non-Javadoc) * @see org.springframework.data.couchbase.repository.query.AbstractReactiveCouchbaseQuery#isLimiting() */ @Override protected boolean isLimiting() { - // TODO - return false; - } - - private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, - boolean isDeleteQuery) { - return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + return false; // not yet implemented } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ResultProcessingConverter.java b/src/main/java/org/springframework/data/couchbase/repository/query/ResultProcessingConverter.java new file mode 100644 index 000000000..dd8e017df --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ResultProcessingConverter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.repository.query; + +import org.reactivestreams.Publisher; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +final class ResultProcessingConverter implements Converter { + + private final ResultProcessor processor; + private final CouchbaseOperationsType operations; + private final EntityInstantiators instantiators; + + public ResultProcessingConverter(ResultProcessor processor, CouchbaseOperationsType operations, + EntityInstantiators instantiators) { + + Assert.notNull(processor, "Processor must not be null!"); + Assert.notNull(operations, "Operations must not be null!"); + Assert.notNull(instantiators, "Instantiators must not be null!"); + + this.processor = processor; + this.operations = operations; + this.instantiators = instantiators; + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Object convert(Object source) { + + ReturnedType returnedType = processor.getReturnedType(); + + if (isVoid(returnedType)) { + + if (source instanceof Mono) { + return ((Mono) source).then(); + } + + if (source instanceof Publisher) { + return Flux.from((Publisher) source).then(); + } + } + + if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { + return source; + } + + CouchbaseConverter cvtr = operations instanceof CouchbaseOperations + ? ((CouchbaseOperations) operations).getConverter() + : ((ReactiveCouchbaseOperations) operations).getConverter(); + if (!cvtr.getMappingContext().hasPersistentEntityFor(returnedType.getReturnedType())) { + return source; + } + + Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), + cvtr.getMappingContext(), instantiators); + + return processor.processResult(source, converter); + } + + static boolean isVoid(ReturnedType returnedType) { + return returnedType.getReturnedType().equals(Void.class); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java index 0411751c6..d52a04b3d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedCouchbaseQuery.java @@ -38,10 +38,6 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery { private final SpelExpressionParser expressionParser; private final QueryMethodEvaluationContextProvider evaluationContextProvider; - - private final boolean isCountQuery; - private final boolean isExistsQuery; - private final boolean isDeleteQuery; private final NamedQueries namedQueries; /** @@ -51,8 +47,8 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery { * @param method must not be {@literal null}. * @param couchbaseOperations must not be {@literal null}. * @param expressionParser must not be {@literal null}. - * @param evaluationContextProvider must not be {@literal null} - * @param namedQueries + * @param evaluationContextProvider must not be {@literal null}. + * @param namedQueries must not be {@literal null}. */ public StringBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations couchbaseOperations, SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider, @@ -64,11 +60,8 @@ public StringBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperation this.expressionParser = expressionParser; this.evaluationContextProvider = evaluationContextProvider; - this.isCountQuery = method.isCountQuery(); - this.isExistsQuery = method.isExistsQuery(); - this.isDeleteQuery = method.isDeleteQuery(); - if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + if (hasAmbiguousProjectionFlags(isCountQuery(), isExistsQuery(), isDeleteQuery())) { throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); } this.namedQueries = namedQueries; @@ -87,37 +80,15 @@ protected Query createQuery(ParametersParameterAccessor accessor) { Query query = creator.createQuery(); if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Created query %s for * fields.", query.export() /*, query.getFieldsObject()*/)); + LOG.debug("Created query " + query.export()); } return query; } - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isCountQuery() - */ @Override - protected boolean isCountQuery() { - return isCountQuery; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isExistsQuery() - */ - @Override - protected boolean isExistsQuery() { - return isExistsQuery; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.repository.query.AbstractCouchbaseQuery#isDeleteQuery() - */ - @Override - protected boolean isDeleteQuery() { - return this.isDeleteQuery; + protected Query createCountQuery(ParametersParameterAccessor accessor) { + return applyQueryMetaAttributesWhenPresent(createQuery(accessor)); } /* @@ -126,11 +97,7 @@ protected boolean isDeleteQuery() { */ @Override protected boolean isLimiting() { - return false; + return false; // not yet implemented } - private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, - boolean isDeleteQuery) { - return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; - } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java index c4d6b2a82..d19f185f9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreator.java @@ -15,10 +15,9 @@ */ package org.springframework.data.couchbase.repository.query; -import static org.springframework.data.couchbase.core.query.QueryCriteria.where; - -import java.util.Iterator; - +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.N1QLExpression; @@ -37,26 +36,25 @@ import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.spel.standard.SpelExpressionParser; -import com.couchbase.client.java.json.JsonArray; -import com.couchbase.client.java.json.JsonObject; -import com.couchbase.client.java.json.JsonValue; +import java.util.Iterator; + +import static org.springframework.data.couchbase.core.query.QueryCriteria.where; /** * @author Michael Reiche - * @since 4.1 */ public class StringN1qlQueryCreator extends AbstractQueryCreator { private final ParameterAccessor accessor; private final MappingContext context; private final SpelExpressionParser parser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; private final StringBasedN1qlQueryParser queryParser; private final QueryMethod queryMethod; private final CouchbaseConverter couchbaseConverter; private final N1QLExpression parsedExpression; public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMethod queryMethod, - CouchbaseConverter couchbaseConverter, String bucketName, SpelExpressionParser spelExpressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider, NamedQueries namedQueries) { @@ -70,6 +68,7 @@ public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMe this.context = couchbaseConverter.getMappingContext(); this.queryMethod = queryMethod; this.couchbaseConverter = couchbaseConverter; + this.evaluationContextProvider = evaluationContextProvider; final String namedQueryName = queryMethod.getNamedQueryName(); String queryString; if (queryMethod.hasInlineN1qlQuery()) { @@ -81,8 +80,8 @@ public StringN1qlQueryCreator(final ParameterAccessor accessor, CouchbaseQueryMe } this.queryParser = new StringBasedN1qlQueryParser(queryString, queryMethod, bucketName, couchbaseConverter, getTypeField(), getTypeValue(), accessor, spelExpressionParser, evaluationContextProvider); - this.parsedExpression = this.queryParser.parsedExpression; this.parser = spelExpressionParser; + this.parsedExpression = this.queryParser.parsedExpression; } protected QueryMethod getQueryMethod() { @@ -99,7 +98,8 @@ protected String getTypeValue() { @Override protected QueryCriteria create(final Part part, final Iterator iterator) { - PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); + PersistentPropertyPath path = context.getPersistentPropertyPath( + part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); return from(part, property, where(path.toDotPath()), iterator); } @@ -110,7 +110,8 @@ protected QueryCriteria and(final Part part, final QueryCriteria base, final Ite return create(part, iterator); } - PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); + PersistentPropertyPath path = context.getPersistentPropertyPath( + part.getProperty()); CouchbasePersistentProperty property = path.getLeafProperty(); return from(part, property, base.and(path.toDotPath()), iterator); @@ -138,10 +139,10 @@ private QueryCriteria from(final Part part, final CouchbasePersistentProperty pr final Part.Type type = part.getType(); switch (type) { - case SIMPLE_PROPERTY: - return criteria; // .eq(parameters.next()); // this will be the dummy from PartTree - default: - throw new IllegalArgumentException("Unsupported keyword!"); + case SIMPLE_PROPERTY: + return criteria; // this will be the dummy from PartTree + default: + throw new IllegalArgumentException("Unsupported keyword!"); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 13b0b7c5f..f42d9ff4f 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -90,14 +90,14 @@ public void setBeanClassLoader(ClassLoader classLoader) { * Returns entity information based on the domain class. * * @param domainClass the class for the entity. - * @param the value type - * @param the id type. + * @param the value type + * @param the id type. * @return entity information for that domain class. */ @Override public CouchbaseEntityInformation getEntityInformation(Class domainClass) { - CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext - .getRequiredPersistentEntity(domainClass); + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext.getRequiredPersistentEntity( + domainClass); return new MappingCouchbaseEntityInformation<>(entity); } @@ -153,8 +153,8 @@ public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluat @Override public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata, final ProjectionFactory factory, final NamedQueries namedQueries) { - final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping - .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); + final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( + metadata.getRepositoryInterface(), metadata.getDomainType()); CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java index 6b317b7b3..8ca423c39 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java @@ -85,14 +85,14 @@ public void setBeanClassLoader(ClassLoader classLoader) { * Returns entity information based on the domain class. * * @param domainClass the class for the entity. - * @param the value type - * @param the id type. + * @param the value type + * @param the id type. * @return entity information for that domain class. */ @Override public CouchbaseEntityInformation getEntityInformation(Class domainClass) { - CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext - .getRequiredPersistentEntity(domainClass); + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext.getRequiredPersistentEntity( + domainClass); return new MappingCouchbaseEntityInformation<>(entity); } @@ -107,8 +107,8 @@ public CouchbaseEntityInformation getEntityInformation(Class d */ @Override protected final Object getTargetRepository(final RepositoryInformation metadata) { - ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping - .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); + ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( + metadata.getRepositoryInterface(), metadata.getDomainType()); CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); SimpleReactiveCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation, couchbaseOperations); @@ -150,8 +150,8 @@ public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluat @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { - final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping - .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); + final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve( + metadata.getRepositoryInterface(), metadata.getDomainType()); ReactiveCouchbaseQueryMethod queryMethod = new ReactiveCouchbaseQueryMethod(method, metadata, factory, mappingContext); diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java index bfead97d8..c87906ca3 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java @@ -16,7 +16,7 @@ package org.springframework.data.couchbase.repository.support; -import static org.springframework.data.couchbase.repository.support.Util.*; +import static org.springframework.data.couchbase.repository.support.Util.hasNonZeroVersionProperty; import java.util.Collection; import java.util.List; 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 33c5c0241..04595c1b6 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -16,8 +16,12 @@ package org.springframework.data.couchbase.core; -import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.data.couchbase.config.BeanNames.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; +import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE; import java.io.IOException; import java.time.Instant; @@ -99,7 +103,6 @@ void findByQueryAll() { .consistentWith(QueryScanConsistency.REQUEST_PLUS).all(); for (User u : foundUsers) { - System.out.println(u); if (!(u.equals(user1) || u.equals(user2))) { // somebody didn't clean up after themselves. couchbaseTemplate.removeById().one(u.getId()); @@ -177,8 +180,7 @@ void removeByMatchingQuery() { Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special")); couchbaseTemplate.removeByQuery(User.class).consistentWith(QueryScanConsistency.REQUEST_PLUS) - .matching(nonSpecialUsers) - .all(); + .matching(nonSpecialUsers).all(); assertNull(couchbaseTemplate.findById(User.class).one(user1.getId())); assertNull(couchbaseTemplate.findById(User.class).one(user2.getId())); 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 b5bafe98c..911aebd70 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -42,7 +42,6 @@ public interface AirportRepository extends PagingAndSortingRepository findAll(); @Override - @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) Airport save(Airport airport); @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) 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 6fd052453..b876d00f8 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Config.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Config.java @@ -16,31 +16,29 @@ package org.springframework.data.couchbase.domain; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.ClusterOptions; -import com.couchbase.client.java.env.ClusterEnvironment; +import java.lang.reflect.InvocationTargetException; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.auditing.DateTimeProvider; -import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService; +import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider; import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.util.TypeInformation; -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; +import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature; +import com.couchbase.client.java.json.JacksonTransformers; /** * @author Michael Nitschinger @@ -190,6 +188,17 @@ public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingConte return converter; } + @Override + @Bean(name = "couchbaseTranslationService") + public TranslationService couchbaseTranslationService() { + final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService(); + jacksonTranslationService.afterPropertiesSet(); + + // for sdk3, we need to ask the mapper _it_ uses to ignore extra fields... + JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return jacksonTranslationService; + } + @Override public String typeKey() { return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter 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 b92847f82..7a050cacd 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -146,13 +146,10 @@ void findBySimpleProperty() { vie = airportRepository.save(vie); List airports = airportRepository.findAllByIata("vie"); assertEquals(1, airports.size()); - System.out.println("findAllByIata(0): " + airports.get(0)); Airport airport1 = airportRepository.findById(airports.get(0).getId()).get(); assertEquals(airport1.getIata(), vie.getIata()); - System.out.println("findById: " + airport1); Airport airport2 = airportRepository.findByIata(airports.get(0).getIata()); assertEquals(airport1.getId(), vie.getId()); - System.out.println("findByIata: " + airport2); } finally { airportRepository.delete(vie); } @@ -172,11 +169,8 @@ public void testCas() { @Test void count() { String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; - Future[] future = new Future[iatas.length]; - ExecutorService executorService = Executors.newFixedThreadPool(iatas.length); try { - Callable[] suppliers = new Callable[iatas.length]; for (int i = 0; i < iatas.length; i++) { Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i].toLowerCase(Locale.ROOT) /* lcao */); 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 819655dcf..1b98996c7 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java @@ -113,13 +113,10 @@ void findBySimpleProperty() { vie = airportRepository.save(vie).block(); List airports = airportRepository.findAllByIata("vie").collectList().block(); assertEquals(1, airports.size()); - System.out.println("findAllByIata(0): " + airports.get(0)); Airport airport1 = airportRepository.findById(airports.get(0).getId()).block(); assertEquals(airport1.getIata(), vie.getIata()); - System.out.println("findById: " + airport1); Airport airport2 = airportRepository.findByIata(airports.get(0).getIata()).block(); assertEquals(airport1.getId(), vie.getId()); - System.out.println("findByIata: " + airport2); } finally { airportRepository.delete(vie).block(); } @@ -149,7 +146,6 @@ void count() { Future[] future = new Future[iatas.size()]; ExecutorService executorService = Executors.newFixedThreadPool(iatas.size()); try { - Callable[] suppliers = new Callable[iatas.size()]; for (String iata : iatas) { Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */); 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 2b42b6fb0..06968b4fc 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 @@ -97,7 +97,6 @@ queryMethod, converter, config().bucketname(), new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries); Query query = creator.createQuery(); - System.out.println(query.toN1qlSelectString(couchbaseTemplate.reactive(), Airline.class, false)); try { Thread.sleep(3000); diff --git a/src/test/resources/integration.properties b/src/test/resources/integration.properties index 6c7dcac19..f097d05bd 100644 --- a/src/test/resources/integration.properties +++ b/src/test/resources/integration.properties @@ -12,4 +12,4 @@ cluster.mocked.numReplicas=1 # Entry point configuration if not managed # value of hostname and ns_server port cluster.unmanaged.seed=127.0.0.1:8091 -cluster.unmanaged.numReplicas=0 \ No newline at end of file +cluster.unmanaged.numReplicas=0