Skip to content

Commit a734480

Browse files
mp911dechristophstrobl
authored andcommitted
Add support for returning SearchResult from repository query methods.
Closes: #4960
1 parent 5745da3 commit a734480

File tree

56 files changed

+2047
-104
lines changed

Some content is hidden

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

56 files changed

+2047
-104
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ <T, R> GeoResults<R> doGeoNear(NearQuery near, Class<?> domainType, String colle
10791079
result.add(geoResult);
10801080
}
10811081

1082-
Distance avgDistance = new Distance(
1082+
Distance avgDistance = Distance.of(
10831083
result.size() == 0 ? 0 : aggregate.divide(new BigDecimal(result.size()), RoundingMode.HALF_UP).doubleValue(),
10841084
near.getMetric());
10851085

@@ -2688,7 +2688,9 @@ protected <S, T> List<T> doFind(String collectionName,
26882688

26892689
if (LOGGER.isDebugEnabled()) {
26902690

2691-
Document mappedSort = preparer instanceof SortingQueryCursorPreparer sqcp ? getMappedSortObject(sqcp.getSortObject(), entity) : null;
2691+
Document mappedSort = preparer instanceof SortingQueryCursorPreparer sqcp
2692+
? getMappedSortObject(sqcp.getSortObject(), entity)
2693+
: null;
26922694
LOGGER.debug(String.format("find using query: %s fields: %s sort: %s for class: %s in collection: %s",
26932695
serializeToJsonSafely(mappedQuery), mappedFields, serializeToJsonSafely(mappedSort), entityClass,
26942696
collectionName));
@@ -3623,7 +3625,7 @@ public GeoResult<T> doWith(Document object) {
36233625

36243626
T doWith = delegate.doWith(object);
36253627

3626-
return new GeoResult<>(doWith, new Distance(distance, metric));
3628+
return new GeoResult<>(doWith, Distance.of(distance, metric));
36273629
}
36283630
}
36293631

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3312,7 +3312,7 @@ public Mono<GeoResult<T>> doWith(Document object) {
33123312

33133313
double distance = getDistance(object);
33143314

3315-
return delegate.doWith(object).map(doWith -> new GeoResult<>(doWith, new Distance(distance, metric)));
3315+
return delegate.doWith(object).map(doWith -> new GeoResult<>(doWith, Distance.of(distance, metric)));
33163316
}
33173317

33183318
double getDistance(Document object) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java

+1
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,5 @@ public Document getRawResults() {
105105
Object object = rawResults.get("serverUsed");
106106
return object instanceof String stringValue ? stringValue : null;
107107
}
108+
108109
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ enum DocumentToCircleConverter implements Converter<Document, Circle> {
270270
Assert.notNull(center, "Center must not be null");
271271
Assert.notNull(radius, "Radius must not be null");
272272

273-
Distance distance = new Distance(toPrimitiveDoubleValue(radius));
273+
Distance distance = Distance.of(toPrimitiveDoubleValue(radius));
274274

275275
if (source.containsKey("metric")) {
276276

@@ -335,7 +335,7 @@ enum DocumentToSphereConverter implements Converter<Document, Sphere> {
335335
Assert.notNull(center, "Center must not be null");
336336
Assert.notNull(radius, "Radius must not be null");
337337

338-
Distance distance = new Distance(toPrimitiveDoubleValue(radius));
338+
Distance distance = Distance.of(toPrimitiveDoubleValue(radius));
339339

340340
if (source.containsKey("metric")) {
341341

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/Sphere.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public Sphere(Point center, Distance radius) {
6363
* @param radius
6464
*/
6565
public Sphere(Point center, double radius) {
66-
this(center, new Distance(radius));
66+
this(center, Distance.of(radius));
6767
}
6868

6969
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.bson.types.Decimal128;
3030
import org.bson.types.ObjectId;
3131
import org.bson.types.Symbol;
32+
3233
import org.springframework.data.mapping.model.SimpleTypeHolder;
3334

3435
import com.mongodb.DBRef;

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.bson.Document;
2121
import org.jspecify.annotations.Nullable;
22+
2223
import org.springframework.data.domain.Pageable;
2324
import org.springframework.data.geo.CustomMetric;
2425
import org.springframework.data.geo.Distance;
@@ -329,7 +330,7 @@ public NearQuery with(Pageable pageable) {
329330
*/
330331
@Contract("_ -> this")
331332
public NearQuery maxDistance(double maxDistance) {
332-
return maxDistance(new Distance(maxDistance, getMetric()));
333+
return maxDistance(Distance.of(maxDistance, getMetric()));
333334
}
334335

335336
/**
@@ -345,7 +346,7 @@ public NearQuery maxDistance(double maxDistance, Metric metric) {
345346

346347
Assert.notNull(metric, "Metric must not be null");
347348

348-
return maxDistance(new Distance(maxDistance, metric));
349+
return maxDistance(Distance.of(maxDistance, metric));
349350
}
350351

351352
/**
@@ -388,7 +389,7 @@ public NearQuery maxDistance(Distance distance) {
388389
*/
389390
@Contract("_ -> this")
390391
public NearQuery minDistance(double minDistance) {
391-
return minDistance(new Distance(minDistance, getMetric()));
392+
return minDistance(Distance.of(minDistance, getMetric()));
392393
}
393394

394395
/**
@@ -405,7 +406,7 @@ public NearQuery minDistance(double minDistance, Metric metric) {
405406

406407
Assert.notNull(metric, "Metric must not be null");
407408

408-
return minDistance(new Distance(minDistance, metric));
409+
return minDistance(Distance.of(minDistance, metric));
409410
}
410411

411412
/**
@@ -611,7 +612,7 @@ public NearQuery withReadPreference(ReadPreference readPreference) {
611612
* Get the {@link ReadConcern} to use. Will return the underlying {@link #query(Query) queries}
612613
* {@link Query#getReadConcern() ReadConcern} if present or the one defined on the {@link NearQuery#readConcern}
613614
* itself.
614-
*
615+
*
615616
* @return can be {@literal null} if none set.
616617
* @since 4.1
617618
* @see ReadConcernAware
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2025 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.mongodb.repository;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
import org.springframework.data.mongodb.core.aggregation.VectorSearchOperation;
26+
27+
/**
28+
* Annotation to declare Vector Search queries directly on repository methods. Vector Search queries are used to search
29+
* for similar documents based on vector embeddings typically returning
30+
* {@link org.springframework.data.domain.SearchResults} and limited by either a
31+
* {@link org.springframework.data.domain.Score} (within) or a {@link org.springframework.data.domain.Range} of scores
32+
* (between).
33+
* <p>
34+
* Vector search must define an index name using the {@link #indexName()} attribute. The index must be created in the
35+
* MongoDB Atlas cluster before executing the query. Any misspelling of the index name will result in returning no
36+
* results.
37+
* <p>
38+
* When using pre-filters, you can either define {@link #filter()} or use query derivation to define the pre-filter.
39+
* {@link org.springframework.data.domain.Vector} and distance parameters are considered once these are present. Vector
40+
* search supports sorting and will consider {@link org.springframework.data.domain.Sort} parameters.
41+
*
42+
* @author Mark Paluch
43+
* @since 5.0
44+
* @see org.springframework.data.domain.Score
45+
* @see org.springframework.data.domain.Vector
46+
* @see org.springframework.data.domain.SearchResults
47+
*/
48+
@Retention(RetentionPolicy.RUNTIME)
49+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
50+
@Documented
51+
@Query
52+
@Hint
53+
public @interface VectorSearch {
54+
55+
/**
56+
* Configuration whether to use
57+
* {@link org.springframework.data.mongodb.core.aggregation.VectorSearchOperation.SearchType#ANN} or
58+
* {@link org.springframework.data.mongodb.core.aggregation.VectorSearchOperation.SearchType#ENN} for the search.
59+
*
60+
* @return the search type to use.
61+
*/
62+
VectorSearchOperation.SearchType searchType() default VectorSearchOperation.SearchType.DEFAULT;
63+
64+
/**
65+
* Name of the Atlas Vector Search index to use. Atlas Vector Search doesn't return results if you misspell the index
66+
* name or if the specified index doesn't already exist on the cluster.
67+
*
68+
* @return name of the Atlas Vector Search index to use.
69+
*/
70+
@AliasFor(annotation = Hint.class, value = "indexName")
71+
String indexName();
72+
73+
/**
74+
* Indexed vector type field to search. This is defaulted from the domain model using the first Vector property found.
75+
*
76+
* @return an empty String by default.
77+
*/
78+
String path() default "";
79+
80+
/**
81+
* Takes a MongoDB JSON (MQL) string defining the pre-filter against indexed fields. Supports Value Expressions. Alias
82+
* for {@link VectorSearch#filter}.
83+
*
84+
* @return an empty String by default.
85+
*/
86+
@AliasFor(annotation = Query.class)
87+
String value() default "";
88+
89+
/**
90+
* Takes a MongoDB JSON (MQL) string defining the pre-filter against indexed fields. Supports Value Expressions. Alias
91+
* for {@link VectorSearch#value}.
92+
*
93+
* @return an empty String by default.
94+
*/
95+
@AliasFor(annotation = Query.class, value = "value")
96+
String filter() default "";
97+
98+
/**
99+
* Number of documents to return in the results. This value can't exceed the value of {@link #numCandidates} if you
100+
* specify {@link #numCandidates}. Limit accepts Value Expressions. A Vector Search method cannot define both,
101+
* {@code limit()} and a {@link org.springframework.data.domain.Limit} parameter. Supports Value Expressions.
102+
*
103+
* @return number of documents to return in the results.
104+
*/
105+
String limit() default "";
106+
107+
/**
108+
* Number of nearest neighbors to use during the search. Value must be less than or equal to ({@code <=})
109+
* {@code 10000}. You can't specify a number less than the {@link #limit() number of documents to return}. We
110+
* recommend that you specify a number at least {@code 20} times higher than the {@link #limit() number of documents
111+
* to return} to increase accuracy.
112+
* <p>
113+
* This over-request pattern is the recommended way to trade off latency and recall in your ANN searches, and we
114+
* recommend tuning this parameter based on your specific dataset size and query requirements. Required if the query
115+
* uses
116+
* {@link org.springframework.data.mongodb.core.aggregation.VectorSearchOperation.SearchType#ANN}/{@link org.springframework.data.mongodb.core.aggregation.VectorSearchOperation.SearchType#DEFAULT}.
117+
* Supports Value Expressions.
118+
*
119+
* @return number of nearest neighbors to use during the search.
120+
*/
121+
String numCandidates() default "";
122+
123+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java

+17
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import org.jspecify.annotations.Nullable;
2626
import org.springframework.data.domain.Pageable;
2727
import org.springframework.data.domain.Range;
28+
import org.springframework.data.domain.Score;
2829
import org.springframework.data.domain.ScrollPosition;
2930
import org.springframework.data.domain.Sort;
31+
import org.springframework.data.domain.Vector;
3032
import org.springframework.data.geo.Distance;
3133
import org.springframework.data.geo.Point;
3234
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
@@ -129,6 +131,21 @@ public Range<Distance> getDistanceRange() {
129131
return null;
130132
}
131133

134+
@Override
135+
public @Nullable Vector getVector() {
136+
return null;
137+
}
138+
139+
@Override
140+
public @Nullable Score getScore() {
141+
return null;
142+
}
143+
144+
@Override
145+
public @Nullable Range<Score> getScoreRange() {
146+
return null;
147+
}
148+
132149
@Override
133150
public @Nullable Point getGeoNearLocation() {
134151
return null;

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoRepositoryContributor.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.apache.commons.logging.Log;
2424
import org.apache.commons.logging.LogFactory;
2525
import org.jspecify.annotations.Nullable;
26-
2726
import org.springframework.core.annotation.AnnotatedElementUtils;
2827
import org.springframework.data.mongodb.core.MongoOperations;
2928
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
@@ -160,10 +159,11 @@ private QueryInteraction createStringQuery(RepositoryInformation repositoryInfor
160159

161160
private static boolean backoff(MongoQueryMethod method) {
162161

163-
boolean skip = method.isGeoNearQuery() || method.isScrollQuery() || method.isStreamQuery();
162+
boolean skip = method.isGeoNearQuery() || method.isScrollQuery() || method.isStreamQuery()
163+
|| method.isSearchQuery();
164164

165165
if (skip && logger.isDebugEnabled()) {
166-
logger.debug("Skipping AOT generation for [%s]. Method is either geo-near, streaming or scrolling query"
166+
logger.debug("Skipping AOT generation for [%s]. Method is either geo-near, streaming, search or scrolling query"
167167
.formatted(method.getName()));
168168
}
169169
return skip;

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
164164
}
165165

166166
@SuppressWarnings("NullAway")
167-
private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery<?> operation) {
167+
MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, FindWithQuery<?> operation) {
168168

169169
if (isDeleteQuery()) {
170170
return new DeleteExecution<>(executableRemove, method);
@@ -345,7 +345,7 @@ private Document bindParameters(String source, ConvertingParameterAccessor acces
345345
* @return never {@literal null}.
346346
* @since 3.4
347347
*/
348-
protected ParameterBindingContext prepareBindingContext(String source, ConvertingParameterAccessor accessor) {
348+
protected ParameterBindingContext prepareBindingContext(String source, MongoParameterAccessor accessor) {
349349

350350
ValueExpressionEvaluator evaluator = getExpressionEvaluatorFor(accessor);
351351
return new ParameterBindingContext(accessor::getBindableValue, evaluator);

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
import java.util.List;
2323

2424
import org.jspecify.annotations.Nullable;
25+
2526
import org.springframework.data.domain.Limit;
2627
import org.springframework.data.domain.Pageable;
2728
import org.springframework.data.domain.Range;
29+
import org.springframework.data.domain.Score;
2830
import org.springframework.data.domain.ScrollPosition;
2931
import org.springframework.data.domain.Sort;
32+
import org.springframework.data.domain.Vector;
3033
import org.springframework.data.geo.Distance;
3134
import org.springframework.data.geo.Point;
3235
import org.springframework.data.mongodb.core.convert.MongoWriter;
@@ -73,6 +76,11 @@ public PotentiallyConvertingIterator iterator() {
7376
return new ConvertingIterator(delegate.iterator());
7477
}
7578

79+
@Override
80+
public Vector getVector() {
81+
return delegate.getVector();
82+
}
83+
7684
@Override
7785
public @Nullable ScrollPosition getScrollPosition() {
7886
return delegate.getScrollPosition();
@@ -95,6 +103,16 @@ public Sort getSort() {
95103
return getConvertedValue(delegate.getBindableValue(index), null);
96104
}
97105

106+
@Override
107+
public @org.jspecify.annotations.Nullable Score getScore() {
108+
return delegate.getScore();
109+
}
110+
111+
@Override
112+
public @org.jspecify.annotations.Nullable Range<Score> getScoreRange() {
113+
return delegate.getScoreRange();
114+
}
115+
98116
@Override
99117
public @Nullable Range<Distance> getDistanceRange() {
100118
return delegate.getDistanceRange();
@@ -208,7 +226,7 @@ private static Collection<?> asCollection(@Nullable Object source) {
208226

209227
if (source instanceof Iterable<?> iterable) {
210228

211-
if(source instanceof Collection<?> collection) {
229+
if (source instanceof Collection<?> collection) {
212230
return new ArrayList<>(collection);
213231
}
214232

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.repository.query;
1717

1818
import org.jspecify.annotations.Nullable;
19+
1920
import org.springframework.data.domain.Range;
2021
import org.springframework.data.geo.Distance;
2122
import org.springframework.data.geo.Point;

0 commit comments

Comments
 (0)