diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java index a02e658d0..2c906c28a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java @@ -16,8 +16,10 @@ package org.springframework.data.couchbase.core; import java.time.Duration; +import java.util.Arrays; import java.util.Collection; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.OneAndAllId; import org.springframework.data.couchbase.core.support.InCollection; import org.springframework.data.couchbase.core.support.WithGetOptions; @@ -33,6 +35,7 @@ * * @author Christoph Strobl * @author Tigran Babloyan + * @author Emilien Bevierre * @since 2.0 */ public interface ExecutableFindByIdOperation { @@ -122,6 +125,17 @@ interface FindByIdWithProjection extends FindByIdInScope, WithProjectionId */ @Override FindByIdInScope project(String... fields); + + /** + * Type-safe variant of {@link #project(String...)} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByIdInScope project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } interface FindByIdWithExpiry extends FindByIdWithProjection, WithExpiry { 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 3fc32d06b..fdb7c2297 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java @@ -16,10 +16,13 @@ package org.springframework.data.couchbase.core; import java.util.List; +import java.util.Arrays; import java.util.Optional; import java.util.stream.Stream; import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition; import org.springframework.data.couchbase.core.support.InCollection; @@ -38,6 +41,7 @@ * Query Operations * * @author Christoph Strobl + * @author Emilien Bevierre * @since 2.0 */ public interface ExecutableFindByQueryOperation { @@ -270,6 +274,17 @@ interface FindByQueryWithProjecting extends FindByQueryWithProjection { * @throws IllegalArgumentException if returnType is {@literal null}. */ FindByQueryWithProjection project(String[] fields); + + /** + * Type-safe variant of {@link #project(String[])} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByQueryWithProjection project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** @@ -288,6 +303,17 @@ interface FindByQueryWithDistinct extends FindByQueryWithProjecting, WithD */ @Override FindByQueryWithProjection distinct(String[] distinctFields); + + /** + * Type-safe variant of {@link #distinct(String[])} using property paths. + * + * @param distinctFields the property paths for distinct fields. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByQueryWithProjection distinct(TypedPropertyPath... distinctFields) { + return distinct(Arrays.stream(distinctFields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperation.java index 39ad8047d..e6e7f727f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperation.java @@ -21,6 +21,7 @@ import java.util.stream.Stream; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.InCollection; import org.springframework.data.couchbase.core.support.InScope; import org.springframework.data.couchbase.core.support.WithSearchConsistency; @@ -167,6 +168,9 @@ interface FindBySearchWithSkip extends FindBySearchWithLimit { interface FindBySearchWithSort extends FindBySearchWithSkip { FindBySearchWithSkip withSort(SearchSort... sort); + +

FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties); } interface FindBySearchWithHighlight extends FindBySearchWithSort { @@ -175,6 +179,14 @@ interface FindBySearchWithHighlight extends FindBySearchWithSort { default FindBySearchWithSort withHighlight(String... fields) { return withHighlight(HighlightStyle.SERVER_DEFAULT, fields); } + +

FindBySearchWithSort withHighlight(HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields); + + default

FindBySearchWithSort withHighlight(TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withHighlight(HighlightStyle.SERVER_DEFAULT, field, additionalFields); + } } interface FindBySearchWithFacets extends FindBySearchWithHighlight { @@ -183,6 +195,9 @@ interface FindBySearchWithFacets extends FindBySearchWithHighlight { interface FindBySearchWithFields extends FindBySearchWithFacets { FindBySearchWithFacets withFields(String... fields); + +

FindBySearchWithFacets withFields(TypedPropertyPath field, + TypedPropertyPath... additionalFields); } interface FindBySearchWithProjection extends FindBySearchWithFields { diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationSupport.java index d1c8427aa..424b2c0fc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationSupport.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.stream.Stream; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.ReactiveFindBySearchOperationSupport.ReactiveFindBySearchSupport; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.util.Assert; @@ -186,8 +187,8 @@ public FindBySearchWithOptions inCollection(final String collection) { @Override public FindBySearchWithQuery withOptions(final SearchOptions options) { return new ExecutableFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options != null ? options : this.options, sort, - highlightStyle, highlightFields, facets, fields, limitSkip); + scanConsistency, scope, collection, options != null ? options : this.options, sort, highlightStyle, + highlightFields, facets, fields, limitSkip); } @Override @@ -215,11 +216,23 @@ public FindBySearchWithSkip withSort(SearchSort... sort) { fields, limitSkip); } + @Override + public

FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + return withSort(SearchPropertyPathSupport.toSearchSorts(template.getConverter(), property, additionalProperties)); + } + @Override public FindBySearchWithSort withHighlight(HighlightStyle style, String... fields) { return new ExecutableFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options, sort, style, fields, facets, - this.fields, limitSkip); + scanConsistency, scope, collection, options, sort, style, fields, facets, this.fields, limitSkip); + } + + @Override + public

FindBySearchWithSort withHighlight(HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withHighlight(style, + SearchPropertyPathSupport.getMappedFieldPaths(template.getConverter(), field, additionalFields)); } @Override @@ -235,5 +248,11 @@ public FindBySearchWithFacets withFields(String... fields) { scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, fields, limitSkip); } + + @Override + public

FindBySearchWithFacets withFields(TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withFields(SearchPropertyPathSupport.getMappedFieldPaths(template.getConverter(), field, additionalFields)); + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperation.java index a176a5b56..06a9c4344 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperation.java @@ -19,17 +19,20 @@ import com.couchbase.client.java.kv.MutateInOptions; import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplicateTo; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.Arrays; import java.util.Collection; /** * Mutate In Operations * * @author Tigran Babloyan + * @author Emilien Bevierre * @since 5.1 */ public interface ExecutableMutateInByIdOperation { @@ -98,6 +101,42 @@ interface MutateInByIdWithPaths extends TerminatingMutateInById, WithMutat * By default the CAS value is not provided. */ MutateInByIdWithPaths withCasProvided(); + + /** + * Type-safe variant of {@link #withRemovePaths(String...)} using property paths. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default MutateInByIdWithPaths withRemovePaths(TypedPropertyPath... removePaths) { + return withRemovePaths(Arrays.stream(removePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withInsertPaths(String...)} using property paths. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default MutateInByIdWithPaths withInsertPaths(TypedPropertyPath... insertPaths) { + return withInsertPaths(Arrays.stream(insertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withUpsertPaths(String...)} using property paths. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default MutateInByIdWithPaths withUpsertPaths(TypedPropertyPath... upsertPaths) { + return withUpsertPaths(Arrays.stream(upsertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withReplacePaths(String...)} using property paths. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default MutateInByIdWithPaths withReplacePaths(TypedPropertyPath... replacePaths) { + return withReplacePaths(Arrays.stream(replacePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java index fb3ab1629..0902aab67 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java @@ -19,8 +19,10 @@ import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.Arrays; import java.util.Collection; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.InCollection; import org.springframework.data.couchbase.core.support.InScope; import org.springframework.data.couchbase.core.support.OneAndAllIdReactive; @@ -36,6 +38,7 @@ * * @author Christoph Strobl * @author Tigran Babloyan + * @author Emilien Bevierre * @since 2.0 */ public interface ReactiveFindByIdOperation { @@ -126,6 +129,17 @@ interface FindByIdWithProjection extends FindByIdInScope, WithProjectionId */ FindByIdInCollection project(String... fields); + /** + * Type-safe variant of {@link #project(String...)} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByIdInCollection project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + } interface FindByIdWithExpiry extends FindByIdWithProjection, WithExpiry { 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 5ef871193..b270877fe 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -18,7 +18,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.Arrays; + import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition; import org.springframework.data.couchbase.core.support.InCollection; @@ -38,6 +41,7 @@ * * @author Michael Nitschinger * @author Michael Reiche + * @author Emilien Bevierre */ public interface ReactiveFindByQueryOperation { @@ -217,6 +221,17 @@ interface FindByQueryWithProjecting extends FindByQueryWithProjection { * @throws IllegalArgumentException if returnType is {@literal null}. */ FindByQueryWithProjection project(String[] fields); + + /** + * Type-safe variant of {@link #project(String[])} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByQueryWithProjection project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** @@ -234,6 +249,17 @@ interface FindByQueryWithDistinct extends FindByQueryWithProjecting, WithD * @throws IllegalArgumentException if field is {@literal null}. */ FindByQueryWithProjection distinct(String[] distinctFields); + + /** + * Type-safe variant of {@link #distinct(String[])} using property paths. + * + * @param distinctFields the property paths for distinct fields. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default FindByQueryWithProjection distinct(TypedPropertyPath... distinctFields) { + return distinct(Arrays.stream(distinctFields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperation.java index 886c1aad1..acfe5b076 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperation.java @@ -21,6 +21,7 @@ import java.util.Map; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.InCollection; import org.springframework.data.couchbase.core.support.InScope; import org.springframework.data.couchbase.core.support.WithSearchConsistency; @@ -192,6 +193,9 @@ interface FindBySearchWithSort extends FindBySearchWithSkip { * @param sort the sort specifications. */ FindBySearchWithSkip withSort(SearchSort... sort); + +

FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties); } /** @@ -214,6 +218,14 @@ interface FindBySearchWithHighlight extends FindBySearchWithSort { default FindBySearchWithSort withHighlight(String... fields) { return withHighlight(HighlightStyle.SERVER_DEFAULT, fields); } + +

FindBySearchWithSort withHighlight(HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields); + + default

FindBySearchWithSort withHighlight(TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withHighlight(HighlightStyle.SERVER_DEFAULT, field, additionalFields); + } } /** @@ -238,6 +250,9 @@ interface FindBySearchWithFields extends FindBySearchWithFacets { * @param fields the field names. */ FindBySearchWithFacets withFields(String... fields); + +

FindBySearchWithFacets withFields(TypedPropertyPath field, + TypedPropertyPath... additionalFields); } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperationSupport.java index 612d09c2f..e46e27be0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindBySearchOperationSupport.java @@ -23,6 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.util.Assert; @@ -106,16 +107,16 @@ static class ReactiveFindBySearchSupport implements ReactiveFindBySearch { public TerminatingFindBySearch matching(SearchRequest searchRequest) { Assert.notNull(searchRequest, "SearchRequest must not be null!"); return new ReactiveFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, - fields, limitSkip, support); + scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, fields, + limitSkip, support); } @Override public FindBySearchWithProjection withIndex(String indexName) { Assert.notNull(indexName, "Index name must not be null!"); return new ReactiveFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, - fields, limitSkip, support); + scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, fields, + limitSkip, support); } @Override @@ -129,8 +130,8 @@ public FindBySearchWithFields as(final Class returnType) { @Override public FindBySearchInScope withConsistency(SearchScanConsistency scanConsistency) { return new ReactiveFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, - fields, limitSkip, support); + scanConsistency, scope, collection, options, sort, highlightStyle, highlightFields, facets, fields, + limitSkip, support); } @Override @@ -150,8 +151,8 @@ public FindBySearchWithOptions inCollection(final String collection) { @Override public FindBySearchWithQuery withOptions(final SearchOptions options) { return new ReactiveFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options != null ? options : this.options, sort, - highlightStyle, highlightFields, facets, fields, limitSkip, support); + scanConsistency, scope, collection, options != null ? options : this.options, sort, highlightStyle, + highlightFields, facets, fields, limitSkip, support); } @Override @@ -179,11 +180,24 @@ public FindBySearchWithSkip withSort(SearchSort... sort) { fields, limitSkip, support); } + @Override + public

FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + return withSort(SearchPropertyPathSupport.toSearchSorts(template.getConverter(), property, additionalProperties)); + } + @Override public FindBySearchWithSort withHighlight(HighlightStyle style, String... fields) { return new ReactiveFindBySearchSupport<>(template, domainType, returnType, indexName, searchRequest, - scanConsistency, scope, collection, options, sort, style, fields, facets, - this.fields, limitSkip, support); + scanConsistency, scope, collection, options, sort, style, fields, facets, this.fields, limitSkip, + support); + } + + @Override + public

FindBySearchWithSort withHighlight(HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withHighlight(style, + SearchPropertyPathSupport.getMappedFieldPaths(template.getConverter(), field, additionalFields)); } @Override @@ -200,6 +214,12 @@ public FindBySearchWithFacets withFields(String... fields) { fields, limitSkip, support); } + @Override + public

FindBySearchWithFacets withFields(TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return withFields(SearchPropertyPathSupport.getMappedFieldPaths(template.getConverter(), field, additionalFields)); + } + @Override public Mono one() { return all().singleOrEmpty(); @@ -223,7 +243,7 @@ public Flux all() { return TransactionalSupport.verifyNotInTransaction("findBySearch") .thenMany(executeSearch() .flatMapMany(ReactiveSearchResult::rows)) - .concatMap(row -> hydrateRow(row)) + .concatMap(this::hydrateRow) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); @@ -331,7 +351,6 @@ private Mono hydrateRow(SearchRow row) { return Mono.empty(); }); } - private Mono executeSearch() { SearchOptions opts = buildSearchOptions(); if (scope != null) { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperation.java index 4069c6202..e69bbe3bc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperation.java @@ -19,17 +19,20 @@ import com.couchbase.client.java.kv.MutateInOptions; import com.couchbase.client.java.kv.PersistTo; import com.couchbase.client.java.kv.ReplicateTo; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.support.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.Arrays; import java.util.Collection; /** * Mutate In Operations * * @author Tigran Babloyan + * @author Emilien Bevierre * @since 5.1 */ public interface ReactiveMutateInByIdOperation { @@ -98,6 +101,38 @@ interface MutateInByIdWithPaths extends TerminatingMutateInById, WithMutat * By default the CAS value is not provided. */ MutateInByIdWithPaths withCasProvided(); + + /** + * Type-safe variant of {@link #withRemovePaths(String...)} using property paths. + * @since 6.1 + */ + default MutateInByIdWithPaths withRemovePaths(TypedPropertyPath... removePaths) { + return withRemovePaths(Arrays.stream(removePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withInsertPaths(String...)} using property paths. + * @since 6.1 + */ + default MutateInByIdWithPaths withInsertPaths(TypedPropertyPath... insertPaths) { + return withInsertPaths(Arrays.stream(insertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withUpsertPaths(String...)} using property paths. + * @since 6.1 + */ + default MutateInByIdWithPaths withUpsertPaths(TypedPropertyPath... upsertPaths) { + return withUpsertPaths(Arrays.stream(upsertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withReplacePaths(String...)} using property paths. + * @since 6.1 + */ + default MutateInByIdWithPaths withReplacePaths(TypedPropertyPath... replacePaths) { + return withReplacePaths(Arrays.stream(replacePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } /** diff --git a/src/main/java/org/springframework/data/couchbase/core/SearchPropertyPathSupport.java b/src/main/java/org/springframework/data/couchbase/core/SearchPropertyPathSupport.java new file mode 100644 index 000000000..2e161a16c --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/SearchPropertyPathSupport.java @@ -0,0 +1,66 @@ +/* + * Copyright 2026-present 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; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.core.TypedPropertyPath; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.mapping.PersistentPropertyPath; + +import com.couchbase.client.java.search.sort.SearchSort; + +final class SearchPropertyPathSupport { + + private SearchPropertyPathSupport() { + } + + static

String getMappedFieldPath(CouchbaseConverter converter, TypedPropertyPath property) { + PersistentPropertyPath path = converter.getMappingContext() + .getPersistentPropertyPath(property); + return path.toDotPath(CouchbasePersistentProperty::getFieldName); + } + + @SafeVarargs + static

String[] getMappedFieldPaths(CouchbaseConverter converter, TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + + List fields = new ArrayList<>(additionalProperties.length + 1); + fields.add(getMappedFieldPath(converter, property)); + + for (TypedPropertyPath additionalProperty : additionalProperties) { + fields.add(getMappedFieldPath(converter, additionalProperty)); + } + + return fields.toArray(String[]::new); + } + + @SafeVarargs + static

SearchSort[] toSearchSorts(CouchbaseConverter converter, TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + + String[] fields = getMappedFieldPaths(converter, property, additionalProperties); + SearchSort[] result = new SearchSort[fields.length]; + + for (int i = 0; i < fields.length; i++) { + result[i] = SearchSort.byField(fields[i]); + } + + return result; + } +} 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 2829ab05c..8d0f73dcb 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 @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.core.TypeInformation; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; @@ -47,6 +48,7 @@ /** * @author Michael Nitschinger * @author Michael Reiche + * @author Emilien Bevierre */ public class Query { @@ -176,6 +178,18 @@ public Query distinct(String[] distinctFields) { return this; } + /** + * Type-safe variant of {@link #distinct(String[])} using property references. + * + * @param distinctFields the property references to use as distinct fields. + * @since 6.1 + */ + @SafeVarargs + public final Query distinct(TypedPropertyPath... distinctFields) { + this.distinctFields = Arrays.stream(distinctFields).map(TypedPropertyPath::toDotPath).toArray(String[]::new); + return this; + } + /** * distinctFields for query (non-null but empty means all fields) ? {@code distinctFields}. * diff --git a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java index ae0c5f3cd..859d669b7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Locale; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.jspecify.annotations.Nullable; import org.springframework.util.CollectionUtils; @@ -39,6 +40,7 @@ * @author Michael Reiche * @author Mauro Monti * @author Shubham Mishra + * @author Emilien Bevierre */ public class QueryCriteria implements QueryCriteriaDefinition { @@ -90,6 +92,18 @@ public static QueryCriteria where(N1QLExpression key) { return new QueryCriteria(null, key, null, null); } + /** + * Static factory method to create a Criteria using a type-safe property path. Accepts method references + * (e.g. {@code Person::getFirstname}) as well as composed paths + * (e.g. {@code PropertyPath.of(Person::getAddress).then(Address::getCity)}). + * + * @param propertyPath the type-safe property path. + * @since 6.1 + */ + public static QueryCriteria where(TypedPropertyPath propertyPath) { + return where(propertyPath.toDotPath()); + } + // wrap criteria (including the criteriaChain) in a new QueryCriteria and set the queryChain of the original criteria // to just itself. The new query will be the value[0] of the original query. private void replaceThisAsWrapperOf(QueryCriteria criteria) { @@ -148,6 +162,17 @@ public QueryCriteria and(N1QLExpression key) { return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.AND); } + /** + * Chain a Criteria using AND with a type-safe property path. Accepts method references + * (e.g. {@code Person::getFirstname}) as well as composed paths. + * + * @param propertyPath the type-safe property path. + * @since 6.1 + */ + public QueryCriteria and(TypedPropertyPath propertyPath) { + return and(propertyPath.toDotPath()); + } + public QueryCriteria and(QueryCriteria criteria) { checkAndAddToCriteriaChain(); QueryCriteria newThis = wrap(this); @@ -183,6 +208,17 @@ public QueryCriteria or(N1QLExpression key) { return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.OR); } + /** + * Chain a Criteria using OR with a type-safe property path. Accepts method references + * (e.g. {@code Person::getFirstname}) as well as composed paths. + * + * @param propertyPath the type-safe property path. + * @since 6.1 + */ + public QueryCriteria or(TypedPropertyPath propertyPath) { + return or(propertyPath.toDotPath()); + } + public QueryCriteria or(QueryCriteria criteria) { checkAndAddToCriteriaChain(); QueryCriteria newThis = wrap(this); diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java b/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java index 92591c9ac..fb05d8431 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithDistinct.java @@ -15,10 +15,15 @@ */ package org.springframework.data.couchbase.core.support; +import java.util.Arrays; + +import org.springframework.data.core.TypedPropertyPath; + /** * Interface for operations that take distinct fields * * @author Michael Reiche + * @author Emilien Bevierre * @param - the entity class */ public interface WithDistinct { @@ -28,4 +33,15 @@ public interface WithDistinct { * @param distinctFields - distinct fields */ Object distinct(String[] distinctFields); + + /** + * Type-safe variant of {@link #distinct(String[])} using property paths. + * + * @param distinctFields the property paths to use as distinct fields. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object distinct(TypedPropertyPath... distinctFields) { + return distinct(Arrays.stream(distinctFields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithMutateInPaths.java b/src/main/java/org/springframework/data/couchbase/core/support/WithMutateInPaths.java index 1f9cc3019..4aeee4688 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithMutateInPaths.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithMutateInPaths.java @@ -15,10 +15,15 @@ */ package org.springframework.data.couchbase.core.support; +import java.util.Arrays; + +import org.springframework.data.core.TypedPropertyPath; + /** * A common interface for all of Insert, Replace, Upsert and Remove mutations that take options. * * @author Tigran Babloyan + * @author Emilien Bevierre * @param - the entity class */ public interface WithMutateInPaths { @@ -29,4 +34,44 @@ public interface WithMutateInPaths { Object withReplacePaths(final String... replacePaths); Object withUpsertPaths(final String... upsertPaths); + + /** + * Type-safe variant of {@link #withRemovePaths(String...)} using property paths. + * + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object withRemovePaths(TypedPropertyPath... removePaths) { + return withRemovePaths(Arrays.stream(removePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withInsertPaths(String...)} using property paths. + * + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object withInsertPaths(TypedPropertyPath... insertPaths) { + return withInsertPaths(Arrays.stream(insertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withReplacePaths(String...)} using property paths. + * + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object withReplacePaths(TypedPropertyPath... replacePaths) { + return withReplacePaths(Arrays.stream(replacePaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + + /** + * Type-safe variant of {@link #withUpsertPaths(String...)} using property paths. + * + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object withUpsertPaths(TypedPropertyPath... upsertPaths) { + return withUpsertPaths(Arrays.stream(upsertPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithProjecting.java b/src/main/java/org/springframework/data/couchbase/core/support/WithProjecting.java index 9e9c9894f..7eb40cf0c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithProjecting.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithProjecting.java @@ -15,13 +15,28 @@ */ package org.springframework.data.couchbase.core.support; +import java.util.Arrays; + +import org.springframework.data.core.TypedPropertyPath; + /** * A common interface for all of Insert, Replace, Upsert that take Projection * * @author Michael Reiche + * @author Emilien Bevierre * @param - the entity class */ public interface WithProjecting { Object project(String[] fields); + /** + * Type-safe variant of {@link #project(String[])} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/WithProjectionId.java b/src/main/java/org/springframework/data/couchbase/core/support/WithProjectionId.java index 16c1704e1..ac9179f04 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/WithProjectionId.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/WithProjectionId.java @@ -15,13 +15,28 @@ */ package org.springframework.data.couchbase.core.support; +import java.util.Arrays; + +import org.springframework.data.core.TypedPropertyPath; + /** * A common interface for those that support project() * * @author Michael Reiche + * @author Emilien Bevierre * @param - the entity class */ public interface WithProjectionId { Object project(String[] fields); + /** + * Type-safe variant of {@link #project(String[])} using property paths. + * + * @param fields the property paths to project. + * @since 6.1 + */ + @SuppressWarnings("unchecked") + default Object project(TypedPropertyPath... fields) { + return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQuery.java index b148adbf0..0169609b5 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQuery.java @@ -128,7 +128,6 @@ private ReactiveFindBySearchOperation.FindBySearchWithConsistency applyPagina } return searchOp; } - private static String resolveIndexName(ReactiveCouchbaseQueryMethod method) { SearchIndex methodAnnotation = method.getAnnotation(SearchIndex.class); if (methodAnnotation != null) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQuery.java index e97ccfc68..66c4187bb 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQuery.java @@ -170,7 +170,6 @@ private ExecutableFindBySearchOperation.FindBySearchWithConsistency applyLimi : searchOp; return limit != null ? withSkipApplied.withLimit(limit) : withSkipApplied; } - private static String resolveIndexName(CouchbaseQueryMethod method) { SearchIndex methodAnnotation = method.getAnnotation(SearchIndex.class); if (methodAnnotation != null) { diff --git a/src/test/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationTests.java b/src/test/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationTests.java index 42af95370..64359a2bf 100644 --- a/src/test/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/ExecutableFindBySearchOperationTests.java @@ -22,6 +22,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; +import org.springframework.data.core.TypedPropertyPath; import com.couchbase.client.java.search.HighlightStyle; import com.couchbase.client.java.search.SearchOptions; @@ -104,4 +105,20 @@ void highlightDefaultMethodUsesServerDefault() { // This is a compile-time check -- the default highlight method delegates to withHighlight(SERVER_DEFAULT, fields) assertNotNull(HighlightStyle.SERVER_DEFAULT); } + + @Test + void typedPropertyReferenceOverloadsAreDeclaredOnInterfaces() throws Exception { + assertNotNull(ExecutableFindBySearchOperation.FindBySearchWithSort.class + .getMethod("withSort", TypedPropertyPath.class, TypedPropertyPath[].class)); + assertNotNull(ExecutableFindBySearchOperation.FindBySearchWithHighlight.class + .getMethod("withHighlight", HighlightStyle.class, TypedPropertyPath.class, TypedPropertyPath[].class)); + assertNotNull(ExecutableFindBySearchOperation.FindBySearchWithFields.class + .getMethod("withFields", TypedPropertyPath.class, TypedPropertyPath[].class)); + assertNotNull(ReactiveFindBySearchOperation.FindBySearchWithSort.class + .getMethod("withSort", TypedPropertyPath.class, TypedPropertyPath[].class)); + assertNotNull(ReactiveFindBySearchOperation.FindBySearchWithHighlight.class + .getMethod("withHighlight", HighlightStyle.class, TypedPropertyPath.class, TypedPropertyPath[].class)); + assertNotNull(ReactiveFindBySearchOperation.FindBySearchWithFields.class + .getMethod("withFields", TypedPropertyPath.class, TypedPropertyPath[].class)); + } } diff --git a/src/test/java/org/springframework/data/couchbase/core/SearchPropertyPathSupportTests.java b/src/test/java/org/springframework/data/couchbase/core/SearchPropertyPathSupportTests.java new file mode 100644 index 000000000..2ad305d0a --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/SearchPropertyPathSupportTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2026-present 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; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.springframework.data.core.PropertyPath; +import org.springframework.data.annotation.Id; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; +import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.data.couchbase.core.mapping.Field; + +class SearchPropertyPathSupportTests { + + private final MappingCouchbaseConverter converter; + + SearchPropertyPathSupportTests() { + CouchbaseMappingContext mappingContext = new CouchbaseMappingContext(); + mappingContext.setInitialEntitySet(Set.of(SearchDocument.class, SearchAddress.class)); + mappingContext.afterPropertiesSet(); + this.converter = new MappingCouchbaseConverter(mappingContext); + } + + @Test + void resolvesAlternativeFieldNames() { + assertEquals("nickname", + SearchPropertyPathSupport.getMappedFieldPath(converter, PropertyPath.of(SearchDocument::getMiddlename))); + } + + @Test + void resolvesNestedPropertyPaths() { + assertEquals("address.city", SearchPropertyPathSupport.getMappedFieldPath(converter, + PropertyPath.of(SearchDocument::getAddress).then(SearchAddress::getCity))); + } + + @Test + void resolvesMultipleMappedFieldPaths() { + assertArrayEquals(new String[] { "nickname", "address.city" }, SearchPropertyPathSupport + .getMappedFieldPaths(converter, PropertyPath.of(SearchDocument::getMiddlename), + PropertyPath.of(SearchDocument::getAddress).then(SearchAddress::getCity))); + } + + @Document + static class SearchDocument { + + @Id String id; + + @Field("nickname") String middlename; + + SearchAddress address; + + String getMiddlename() { + return middlename; + } + + SearchAddress getAddress() { + return address; + } + } + + @Document + static class SearchAddress { + + String city; + + String getCity() { + return city; + } + } +} diff --git a/src/test/java/org/springframework/data/couchbase/core/query/TypeSafePropertyReferenceTests.java b/src/test/java/org/springframework/data/couchbase/core/query/TypeSafePropertyReferenceTests.java new file mode 100644 index 000000000..1456489ff --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/query/TypeSafePropertyReferenceTests.java @@ -0,0 +1,181 @@ +/* + * Copyright 2026-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.data.couchbase.core.query.QueryCriteria.where; + +import org.junit.jupiter.api.Test; + +import org.springframework.data.core.PropertyPath; +import org.springframework.data.core.TypedPropertyPath; +import org.springframework.data.couchbase.domain.Address; +import org.springframework.data.couchbase.domain.Airport; +import org.springframework.data.couchbase.domain.Person; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.domain.Sort; + +/** + * Unit tests for type-safe property references in QueryCriteria, Query, and Sort. + * + * @author Emilien Bevierre + */ +class TypeSafePropertyReferenceTests { + + // --- QueryCriteria.where() --- + + @Test + void whereWithMethodReference() { + QueryCriteria c = where(User::getFirstname).is("Cynthia"); + assertEquals("firstname = \"Cynthia\"", c.export()); + } + + @Test + void whereWithNestedPropertyPath() { + TypedPropertyPath cityPath = PropertyPath.of(Person::getAddress).then(Address::getCity); + QueryCriteria c = where(cityPath).is("Paris"); + assertEquals("address.city = \"Paris\"", c.export()); + } + + // --- QueryCriteria.and() --- + + @Test + void andWithMethodReference() { + QueryCriteria c = where(User::getFirstname).is("Charles") + .and(User::getLastname).is("Darwin"); + assertEquals("firstname = \"Charles\" and lastname = \"Darwin\"", c.export()); + } + + @Test + void andWithNestedPropertyPath() { + TypedPropertyPath streetPath = PropertyPath.of(Person::getAddress).then(Address::getStreet); + QueryCriteria c = where(Person::getFirstname).is("Oliver") + .and(streetPath).is("Main St"); + assertEquals("firstname = \"Oliver\" and address.street = \"Main St\"", c.export()); + } + + // --- QueryCriteria.or() --- + + @Test + void orWithMethodReference() { + QueryCriteria c = where(User::getFirstname).is("Oliver") + .or(User::getLastname).is("Gierke"); + assertEquals("firstname = \"Oliver\" or lastname = \"Gierke\"", c.export()); + } + + @Test + void orWithNestedPropertyPath() { + TypedPropertyPath cityPath = PropertyPath.of(Person::getAddress).then(Address::getCity); + QueryCriteria c = where(Person::getFirstname).is("Oliver") + .or(cityPath).is("Berlin"); + assertEquals("firstname = \"Oliver\" or address.city = \"Berlin\"", c.export()); + } + + // --- Query.distinct() with TypedPropertyPath --- + + @Test + void queryDistinctWithTypedPropertyPath() { + Query q = new Query(); + q.distinct(PropertyPath.of(Airport::getIata)); + assertEquals(1, q.getDistinctFields().length); + assertEquals("iata", q.getDistinctFields()[0]); + } + + @Test + void queryDistinctWithNestedTypedPropertyPath() { + Query q = new Query(); + q.distinct(PropertyPath.of(Person::getAddress).then(Address::getCity)); + assertEquals(1, q.getDistinctFields().length); + assertEquals("address.city", q.getDistinctFields()[0]); + } + + @Test + void queryDistinctWithMultipleTypedPropertyPaths() { + Query q = new Query(); + q.distinct(PropertyPath.of(Airport::getIata), PropertyPath.of(Airport::getIcao)); + assertEquals(2, q.getDistinctFields().length); + assertEquals("iata", q.getDistinctFields()[0]); + assertEquals("icao", q.getDistinctFields()[1]); + } + + // --- Sort.by() with TypedPropertyPath (Spring Data Commons API) --- + + @Test + void sortByTypedPropertyPath() { + Sort sort = Sort.by(PropertyPath.of(User::getFirstname)); + assertEquals("firstname: ASC", sort.iterator().next().toString()); + } + + @Test + void sortByMultipleTypedPropertyPaths() { + Sort sort = Sort.by(PropertyPath.of(User::getFirstname), PropertyPath.of(User::getLastname)); + var orders = sort.toList(); + assertEquals(2, orders.size()); + assertEquals("firstname: ASC", orders.get(0).toString()); + assertEquals("lastname: ASC", orders.get(1).toString()); + } + + @Test + void sortByNestedTypedPropertyPath() { + Sort sort = Sort.by(PropertyPath.of(Person::getAddress).then(Address::getCity)); + assertEquals("address.city: ASC", sort.iterator().next().toString()); + } + + @Test + void sortOrderWithTypedPropertyPath() { + Sort.Order order = Sort.Order.desc(PropertyPath.of(User::getFirstname)); + assertEquals("firstname: DESC", order.toString()); + } + + // --- Sort with Query --- + + @Test + void querySortWithTypedPropertyPath() { + Query query = new Query(); + query.with(Sort.by(PropertyPath.of(Airport::getIata))); + StringBuilder sb = new StringBuilder(); + query.appendSort(sb); + assertEquals(" ORDER BY iata ASC", sb.toString()); + } + + @Test + void querySortDescWithTypedPropertyPath() { + Query query = new Query(); + query.with(Sort.by(Sort.Direction.DESC, PropertyPath.of(Airport::getIata))); + StringBuilder sb = new StringBuilder(); + query.appendSort(sb); + assertEquals(" ORDER BY iata DESC", sb.toString()); + } + + @Test + void backwardsCompatibilityStringBased() { + QueryCriteria c = where("firstname").is("Oliver"); + assertEquals("firstname = \"Oliver\"", c.export()); + + Query q = new Query(); + q.distinct(new String[] { "iata", "icao" }); + assertEquals(2, q.getDistinctFields().length); + } + + @Test + void complexChainWithMixedApproach() { + TypedPropertyPath cityPath = PropertyPath.of(Person::getAddress).then(Address::getCity); + QueryCriteria c = where(Person::getFirstname).is("Oliver") + .and("lastname").is("Gierke") + .or(cityPath).is("Berlin"); + assertEquals("firstname = \"Oliver\" and lastname = \"Gierke\" or address.city = \"Berlin\"", c.export()); + } +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQueryTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQueryTests.java index 5bacee0b4..1ebc35b43 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQueryTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveSearchBasedCouchbaseQueryTests.java @@ -33,6 +33,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.repository.Search; import org.springframework.data.couchbase.repository.SearchIndex; @@ -267,12 +268,25 @@ public ReactiveFindBySearchOperation.FindBySearchWithSkip withSort( return this; } + @Override + public

ReactiveFindBySearchOperation.FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + return this; + } + @Override public ReactiveFindBySearchOperation.FindBySearchWithSort withHighlight( com.couchbase.client.java.search.HighlightStyle style, String... fields) { return this; } + @Override + public

ReactiveFindBySearchOperation.FindBySearchWithSort withHighlight( + com.couchbase.client.java.search.HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return this; + } + @Override public ReactiveFindBySearchOperation.FindBySearchWithHighlight withFacets( Map facets) { @@ -284,6 +298,12 @@ public ReactiveFindBySearchOperation.FindBySearchWithFacets withFields(String return this; } + @Override + public

ReactiveFindBySearchOperation.FindBySearchWithFacets withFields( + TypedPropertyPath field, TypedPropertyPath... additionalFields) { + return this; + } + @SuppressWarnings("unchecked") private List values() { int fromIndex = Math.min(skip != null ? skip : 0, results.size()); diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQueryTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQueryTests.java index 2fd786c60..02299a399 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQueryTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/SearchBasedCouchbaseQueryTests.java @@ -32,6 +32,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.repository.Search; import org.springframework.data.couchbase.repository.SearchIndex; @@ -390,12 +391,25 @@ public ExecutableFindBySearchOperation.FindBySearchWithSkip withSort( return this; } + @Override + public

ExecutableFindBySearchOperation.FindBySearchWithSkip withSort(TypedPropertyPath property, + TypedPropertyPath... additionalProperties) { + return this; + } + @Override public ExecutableFindBySearchOperation.FindBySearchWithSort withHighlight( com.couchbase.client.java.search.HighlightStyle style, String... fields) { return this; } + @Override + public

ExecutableFindBySearchOperation.FindBySearchWithSort withHighlight( + com.couchbase.client.java.search.HighlightStyle style, TypedPropertyPath field, + TypedPropertyPath... additionalFields) { + return this; + } + @Override public ExecutableFindBySearchOperation.FindBySearchWithHighlight withFacets( Map facets) { @@ -407,6 +421,12 @@ public ExecutableFindBySearchOperation.FindBySearchWithFacets withFields(Stri return this; } + @Override + public

ExecutableFindBySearchOperation.FindBySearchWithFacets withFields( + TypedPropertyPath field, TypedPropertyPath... additionalFields) { + return this; + } + @SuppressWarnings("unchecked") private List values() { int fromIndex = Math.min(skip != null ? skip : 0, results.size());