Skip to content

Commit 3cd9c0d

Browse files
authored
Add support for FTS (#2127)
Signed-off-by: Emilien Bevierre <emilien.bevierre@couchbase.com>
1 parent 3001050 commit 3cd9c0d

51 files changed

Lines changed: 4877 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* @author Michael Reiche
5050
* @author Jorge Rodriguez Martin
5151
* @author Tigran Babloyan
52+
* @author Emilien Bevierre
5253
* @since 3.0
5354
*/
5455
public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContextAware {
@@ -188,6 +189,11 @@ public <T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
188189
return new ExecutableFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
189190
}
190191

192+
@Override
193+
public <T> ExecutableFindBySearch<T> findBySearch(Class<T> domainType) {
194+
return new ExecutableFindBySearchOperationSupport(this).findBySearch(domainType);
195+
}
196+
191197
@Override
192198
@Deprecated
193199
public ExecutableRemoveById removeById() {

src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package org.springframework.data.couchbase.core;
1717

1818
import java.time.Duration;
19+
import java.util.Arrays;
1920
import java.util.Collection;
2021

22+
import org.springframework.data.core.TypedPropertyPath;
2123
import org.springframework.data.couchbase.core.support.OneAndAllId;
2224
import org.springframework.data.couchbase.core.support.InCollection;
2325
import org.springframework.data.couchbase.core.support.WithGetOptions;
@@ -33,6 +35,7 @@
3335
*
3436
* @author Christoph Strobl
3537
* @author Tigran Babloyan
38+
* @author Emilien Bevierre
3639
* @since 2.0
3740
*/
3841
public interface ExecutableFindByIdOperation {
@@ -122,6 +125,17 @@ interface FindByIdWithProjection<T> extends FindByIdInScope<T>, WithProjectionId
122125
*/
123126
@Override
124127
FindByIdInScope<T> project(String... fields);
128+
129+
/**
130+
* Type-safe variant of {@link #project(String...)} using property paths.
131+
*
132+
* @param fields the property paths to project.
133+
* @since 6.1
134+
*/
135+
@SuppressWarnings("unchecked")
136+
default FindByIdInScope<T> project(TypedPropertyPath<T, ?>... fields) {
137+
return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new));
138+
}
125139
}
126140

127141
interface FindByIdWithExpiry<T> extends FindByIdWithProjection<T>, WithExpiry<T> {

src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.couchbase.core;
1717

18+
import org.springframework.data.core.TypedPropertyPath;
1819
import java.time.Duration;
1920
import java.util.Arrays;
2021
import java.util.Collection;
@@ -98,6 +99,13 @@ public FindByIdInScope<T> project(String... fields) {
9899
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields), expiry, lockDuration);
99100
}
100101

102+
@Override
103+
@SafeVarargs
104+
// maps property references to stored field names (honoring @Field aliases) via the converter
105+
public final FindByIdInScope<T> project(TypedPropertyPath<T, ?>... fields) {
106+
return project(PropertyPathSupport.getMappedFieldPaths(template.getConverter(), fields));
107+
}
108+
101109
@Override
102110
public FindByIdWithProjection<T> withExpiry(final Duration expiry) {
103111
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields,

src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
*/
1616
package org.springframework.data.couchbase.core;
1717

18+
import java.util.Arrays;
1819
import java.util.List;
1920
import java.util.Optional;
2021
import java.util.stream.Stream;
2122

2223
import org.springframework.dao.IncorrectResultSizeDataAccessException;
24+
25+
import org.springframework.data.core.TypedPropertyPath;
2326
import org.springframework.data.couchbase.core.query.Query;
2427
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
2528
import org.springframework.data.couchbase.core.support.InCollection;
@@ -38,6 +41,7 @@
3841
* Query Operations
3942
*
4043
* @author Christoph Strobl
44+
* @author Emilien Bevierre
4145
* @since 2.0
4246
*/
4347
public interface ExecutableFindByQueryOperation {
@@ -270,6 +274,17 @@ interface FindByQueryWithProjecting<T> extends FindByQueryWithProjection<T> {
270274
* @throws IllegalArgumentException if returnType is {@literal null}.
271275
*/
272276
FindByQueryWithProjection<T> project(String[] fields);
277+
278+
/**
279+
* Type-safe variant of {@link #project(String[])} using property paths.
280+
*
281+
* @param fields the property paths to project.
282+
* @since 6.1
283+
*/
284+
@SuppressWarnings("unchecked")
285+
default FindByQueryWithProjection<T> project(TypedPropertyPath<T, ?>... fields) {
286+
return project(Arrays.stream(fields).map(TypedPropertyPath::toDotPath).toArray(String[]::new));
287+
}
273288
}
274289

275290
/**
@@ -288,6 +303,17 @@ interface FindByQueryWithDistinct<T> extends FindByQueryWithProjecting<T>, WithD
288303
*/
289304
@Override
290305
FindByQueryWithProjection<T> distinct(String[] distinctFields);
306+
307+
/**
308+
* Type-safe variant of {@link #distinct(String[])} using property paths.
309+
*
310+
* @param distinctFields the property paths for distinct fields.
311+
* @since 6.1
312+
*/
313+
@SuppressWarnings("unchecked")
314+
default FindByQueryWithProjection<T> distinct(TypedPropertyPath<T, ?>... distinctFields) {
315+
return distinct(Arrays.stream(distinctFields).map(TypedPropertyPath::toDotPath).toArray(String[]::new));
316+
}
291317
}
292318

293319
/**

src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.couchbase.core;
1717

18+
import org.springframework.data.core.TypedPropertyPath;
1819
import java.util.List;
1920
import java.util.stream.Stream;
2021

@@ -147,6 +148,20 @@ public FindByQueryWithProjection<T> distinct(final String[] distinctFields) {
147148
collection, options, dFields, fields);
148149
}
149150

151+
@Override
152+
@SafeVarargs
153+
// maps property references to stored field names (honoring @Field aliases) via the converter
154+
public final FindByQueryWithProjection<T> project(TypedPropertyPath<T, ?>... fields) {
155+
return project(PropertyPathSupport.getMappedFieldPaths(template.getConverter(), fields));
156+
}
157+
158+
@Override
159+
@SafeVarargs
160+
// maps property references to stored field names (honoring @Field aliases) via the converter
161+
public final FindByQueryWithProjection<T> distinct(TypedPropertyPath<T, ?>... distinctFields) {
162+
return distinct(PropertyPathSupport.getMappedFieldPaths(template.getConverter(), distinctFields));
163+
}
164+
150165
@Override
151166
public Stream<T> stream() {
152167
return reactiveSupport.all().toStream();
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2025-present the original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.core;
17+
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
import java.util.stream.Stream;
22+
23+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
24+
import org.springframework.data.core.TypedPropertyPath;
25+
import org.springframework.data.couchbase.core.support.InCollection;
26+
import org.springframework.data.couchbase.core.support.InScope;
27+
import org.springframework.data.couchbase.core.support.WithSearchConsistency;
28+
import org.springframework.data.couchbase.core.support.WithSearchOptions;
29+
import org.springframework.data.couchbase.core.support.WithSearchQuery;
30+
import org.jspecify.annotations.Nullable;
31+
32+
import com.couchbase.client.java.search.HighlightStyle;
33+
import com.couchbase.client.java.search.SearchOptions;
34+
import com.couchbase.client.java.search.SearchRequest;
35+
import com.couchbase.client.java.search.SearchScanConsistency;
36+
import com.couchbase.client.java.search.facet.SearchFacet;
37+
import com.couchbase.client.java.search.result.SearchRow;
38+
import com.couchbase.client.java.search.sort.SearchSort;
39+
40+
/**
41+
* Full-Text Search (FTS) Operations (Blocking)
42+
*
43+
* @author Emilien Bevierre
44+
* @since 6.2
45+
*/
46+
public interface ExecutableFindBySearchOperation {
47+
48+
/**
49+
* Queries the Full-Text Search (FTS) service.
50+
*
51+
* @param domainType the entity type to use for the results.
52+
*/
53+
<T> ExecutableFindBySearch<T> findBySearch(Class<T> domainType);
54+
55+
interface TerminatingFindBySearch<T> {
56+
57+
/**
58+
* Get exactly zero or one result.
59+
*
60+
* @return {@link Optional#empty()} if no match found.
61+
* @throws IncorrectResultSizeDataAccessException if more than one match found.
62+
*/
63+
default Optional<T> one() {
64+
return Optional.ofNullable(oneValue());
65+
}
66+
67+
/**
68+
* Get exactly zero or one result.
69+
*
70+
* @return {@literal null} if no match found.
71+
* @throws IncorrectResultSizeDataAccessException if more than one match found.
72+
*/
73+
@Nullable
74+
T oneValue();
75+
76+
/**
77+
* Get the first or no result.
78+
*
79+
* @return {@link Optional#empty()} if no match found.
80+
*/
81+
default Optional<T> first() {
82+
return Optional.ofNullable(firstValue());
83+
}
84+
85+
/**
86+
* Get the first or no result.
87+
*
88+
* @return {@literal null} if no match found.
89+
*/
90+
@Nullable
91+
T firstValue();
92+
93+
/**
94+
* Get all matching elements, hydrated as entities via KV GET.
95+
* <p>
96+
* If no limit is specified via {@code withLimit(...)} or {@code withOptions(...)}, a default limit of 10,000 is
97+
* applied (the FTS service would otherwise return only its default of 10 hits).
98+
*
99+
* @return never {@literal null}.
100+
*/
101+
List<T> all();
102+
103+
/**
104+
* Stream all matching elements.
105+
*
106+
* @return a {@link Stream} of results. Never {@literal null}.
107+
*/
108+
Stream<T> stream();
109+
110+
/**
111+
* Get the number of matching elements.
112+
*
113+
* @return total number of matching elements.
114+
*/
115+
long count();
116+
117+
/**
118+
* Check for the presence of matching elements.
119+
*
120+
* @return {@literal true} if at least one matching element exists.
121+
*/
122+
boolean exists();
123+
124+
/**
125+
* Get raw FTS search rows (without entity hydration).
126+
*
127+
* @return never {@literal null}.
128+
*/
129+
List<SearchRow> rows();
130+
131+
/**
132+
* Get a combined result including hydrated entities, raw rows, metadata, and facet results.
133+
*
134+
* @return a {@link SearchResult} containing the full response.
135+
*/
136+
SearchResult<T> result();
137+
}
138+
139+
interface FindBySearchWithQuery<T> extends TerminatingFindBySearch<T>, WithSearchQuery<T> {
140+
@Override
141+
TerminatingFindBySearch<T> matching(SearchRequest searchRequest);
142+
}
143+
144+
interface FindBySearchWithOptions<T> extends FindBySearchWithQuery<T>, WithSearchOptions<T> {
145+
@Override
146+
FindBySearchWithQuery<T> withOptions(SearchOptions options);
147+
}
148+
149+
interface FindBySearchInCollection<T> extends FindBySearchWithOptions<T>, InCollection<T> {
150+
@Override
151+
FindBySearchWithOptions<T> inCollection(String collection);
152+
}
153+
154+
interface FindBySearchInScope<T> extends FindBySearchInCollection<T>, InScope<T> {
155+
@Override
156+
FindBySearchInCollection<T> inScope(String scope);
157+
}
158+
159+
interface FindBySearchWithConsistency<T> extends FindBySearchInScope<T>, WithSearchConsistency<T> {
160+
@Override
161+
FindBySearchInScope<T> withConsistency(SearchScanConsistency scanConsistency);
162+
}
163+
164+
interface FindBySearchWithLimit<T> extends FindBySearchWithConsistency<T> {
165+
FindBySearchWithConsistency<T> withLimit(int limit);
166+
}
167+
168+
interface FindBySearchWithSkip<T> extends FindBySearchWithLimit<T> {
169+
FindBySearchWithLimit<T> withSkip(int skip);
170+
}
171+
172+
interface FindBySearchWithSort<T> extends FindBySearchWithSkip<T> {
173+
FindBySearchWithSkip<T> withSort(SearchSort... sort);
174+
175+
<P> FindBySearchWithSkip<T> withSort(TypedPropertyPath<P, ?> property,
176+
TypedPropertyPath<P, ?>... additionalProperties);
177+
}
178+
179+
interface FindBySearchWithHighlight<T> extends FindBySearchWithSort<T> {
180+
FindBySearchWithSort<T> withHighlight(HighlightStyle style, String... fields);
181+
182+
default FindBySearchWithSort<T> withHighlight(String... fields) {
183+
return withHighlight(HighlightStyle.SERVER_DEFAULT, fields);
184+
}
185+
186+
<P> FindBySearchWithSort<T> withHighlight(HighlightStyle style, TypedPropertyPath<P, ?> field,
187+
TypedPropertyPath<P, ?>... additionalFields);
188+
189+
default <P> FindBySearchWithSort<T> withHighlight(TypedPropertyPath<P, ?> field,
190+
TypedPropertyPath<P, ?>... additionalFields) {
191+
return withHighlight(HighlightStyle.SERVER_DEFAULT, field, additionalFields);
192+
}
193+
}
194+
195+
interface FindBySearchWithFacets<T> extends FindBySearchWithHighlight<T> {
196+
FindBySearchWithHighlight<T> withFacets(Map<String, SearchFacet> facets);
197+
}
198+
199+
interface FindBySearchWithFields<T> extends FindBySearchWithFacets<T> {
200+
FindBySearchWithFacets<T> withFields(String... fields);
201+
202+
<P> FindBySearchWithFacets<T> withFields(TypedPropertyPath<P, ?> field,
203+
TypedPropertyPath<P, ?>... additionalFields);
204+
}
205+
206+
interface FindBySearchWithProjection<T> extends FindBySearchWithFields<T> {
207+
<R> FindBySearchWithFields<R> as(Class<R> returnType);
208+
}
209+
210+
interface FindBySearchWithIndex<T> extends FindBySearchWithProjection<T> {
211+
FindBySearchWithProjection<T> withIndex(String indexName);
212+
}
213+
214+
interface ExecutableFindBySearch<T> extends FindBySearchWithIndex<T> {}
215+
}

0 commit comments

Comments
 (0)