diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 98516a5ba..86bd73ae7 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -131,6 +131,13 @@
true
+
+ org.awaitility
+ awaitility
+ 4.2.2
+ test
+
+
io.reactivex.rxjava3rxjava
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index 99c763540..fd05cd5b1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -185,8 +185,8 @@ import com.mongodb.client.result.UpdateResult;
* @author Michael Krog
* @author Jakub Zurawa
*/
-public class MongoTemplate
- implements MongoOperations, ApplicationContextAware, IndexOperationsProvider, SearchIndexOperationsProvider, ReadPreferenceAware {
+public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider,
+ SearchIndexOperationsProvider, ReadPreferenceAware {
private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class);
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
@@ -771,6 +771,21 @@ public class MongoTemplate
return indexOps(getCollectionName(entityClass), entityClass);
}
+ @Override
+ public SearchIndexOperations searchIndexOps(String collectionName) {
+ return searchIndexOps(null, collectionName);
+ }
+
+ @Override
+ public SearchIndexOperations searchIndexOps(Class> type) {
+ return new DefaultSearchIndexOperations(this, type);
+ }
+
+ @Override
+ public SearchIndexOperations searchIndexOps(@Nullable Class> type, String collectionName) {
+ return new DefaultSearchIndexOperations(this, collectionName, type);
+ }
+
@Override
public BulkOperations bulkOps(BulkMode mode, String collectionName) {
return bulkOps(mode, null, collectionName);
@@ -1316,7 +1331,7 @@ public class MongoTemplate
if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking)) {
if (wc == null || wc.getWObject() == null
- || (wc.getWObject()instanceof Number concern && concern.intValue() < 1)) {
+ || (wc.getWObject() instanceof Number concern && concern.intValue() < 1)) {
return WriteConcern.ACKNOWLEDGED;
}
}
@@ -1968,7 +1983,8 @@ public class MongoTemplate
}
if (mapReduceOptions.getOutputSharded().isPresent()) {
- MongoCompatibilityAdapter.mapReduceIterableAdapter(mapReduce).sharded(mapReduceOptions.getOutputSharded().get());
+ MongoCompatibilityAdapter.mapReduceIterableAdapter(mapReduce)
+ .sharded(mapReduceOptions.getOutputSharded().get());
}
if (StringUtils.hasText(mapReduceOptions.getOutputCollection()) && !mapReduceOptions.usesInlineOutput()) {
@@ -2067,7 +2083,7 @@ public class MongoTemplate
}
@Override
- public UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName){
+ public UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName) {
Assert.notNull(replacement, "Replacement must not be null");
return replace(query, (Class) ClassUtils.getUserClass(replacement), replacement, options, collectionName);
@@ -2743,8 +2759,7 @@ public class MongoTemplate
LOGGER.debug(String.format(
"findAndModify using query: %s fields: %s sort: %s for class: %s and update: %s in collection: %s",
serializeToJsonSafely(mappedQuery), fields, serializeToJsonSafely(sort), entityClass,
- serializeToJsonSafely(mappedUpdate),
- collectionName));
+ serializeToJsonSafely(mappedUpdate), collectionName));
}
return executeFindOneInternal(
@@ -3013,21 +3028,6 @@ public class MongoTemplate
return resolved == null ? ex : resolved;
}
- @Override
- public SearchIndexOperations searchIndexOps(String collectionName) {
- return searchIndexOps(null, collectionName);
- }
-
- @Override
- public SearchIndexOperations searchIndexOps(Class> type) {
- return new DefaultSearchIndexOperations(this, type);
- }
-
- @Override
- public SearchIndexOperations searchIndexOps(Class> type, String collectionName) {
- return new DefaultSearchIndexOperations(this, collectionName, type);
- }
-
// Callback implementations
/**
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
index f3984f3fd..45de38ed2 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
@@ -381,9 +381,9 @@ public class Aggregation {
}
/**
- * Factory method to create a new {@link UnwindOperation} for the field with the given name, including the name of a new
- * field to hold the array index of the element as {@code arrayIndex} using {@code preserveNullAndEmptyArrays}. Note
- * that extended unwind is supported in MongoDB version 3.2+.
+ * Factory method to create a new {@link UnwindOperation} for the field with the given name, including the name of a
+ * new field to hold the array index of the element as {@code arrayIndex} using {@code preserveNullAndEmptyArrays}.
+ * Note that extended unwind is supported in MongoDB version 3.2+.
*
* @param field must not be {@literal null} or empty.
* @param arrayIndex must not be {@literal null} or empty.
@@ -428,6 +428,20 @@ public class Aggregation {
return GraphLookupOperation.builder().from(fromCollection);
}
+ /**
+ * Creates a new {@link VectorSearchOperation} by starting from the {@code indexName} to use.
+ *
+ * @param indexName must not be {@literal null} or empty.
+ * @return new instance of {@link VectorSearchOperation.PathContributor}.
+ * @since 4.5
+ */
+ public static VectorSearchOperation.PathContributor vectorSearch(String indexName) {
+
+ Assert.hasText(indexName, "Index name must not be null or empty");
+
+ return VectorSearchOperation.search(indexName);
+ }
+
/**
* Factory method to create a new {@link SortOperation} for the given {@link Sort}.
*
@@ -669,14 +683,14 @@ public class Aggregation {
/**
* Entrypoint for creating {@link LookupOperation $lookup} using a fluent builder API.
+ *
*
+ *
* @return new instance of {@link LookupOperationBuilder}.
* @since 4.1
*/
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperation.java
index 75844ca47..c7d984d47 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperation.java
@@ -23,18 +23,313 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import org.bson.BinaryVector;
import org.bson.Document;
+
import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Vector;
+import org.springframework.data.mongodb.core.mapping.MongoVector;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
+import org.springframework.lang.Contract;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
+ * Performs a semantic search on data in your Atlas cluster. This stage is only available for Atlas Vector Search.
+ * Vector data must be less than or equal to 4096 dimensions in width.
+ *
+ *
* @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 4.5
*/
public class VectorSearchOperation implements AggregationOperation {
+ private final SearchType searchType;
+ private final @Nullable CriteriaDefinition filter;
+ private final String indexName;
+ private final Limit limit;
+ private final @Nullable Integer numCandidates;
+ private final QueryPaths path;
+ private final Vector vector;
+ private final String score;
+ private final Consumer scoreCriteria;
+
+ private VectorSearchOperation(SearchType searchType, @Nullable CriteriaDefinition filter, String indexName,
+ Limit limit, @Nullable Integer numCandidates, QueryPaths path, Vector vector, @Nullable String searchScore,
+ Consumer scoreCriteria) {
+
+ this.searchType = searchType;
+ this.filter = filter;
+ this.indexName = indexName;
+ this.limit = limit;
+ this.numCandidates = numCandidates;
+ this.path = path;
+ this.vector = vector;
+ this.score = searchScore;
+ this.scoreCriteria = scoreCriteria;
+ }
+
+ VectorSearchOperation(String indexName, QueryPaths path, Limit limit, Vector vector) {
+ this(SearchType.DEFAULT, null, indexName, limit, null, path, vector, null, null);
+ }
+
+ /**
+ * Entrypoint to build a {@link VectorSearchOperation} starting from the {@code index} name to search. Atlas Vector
+ * Search doesn't return results if you misspell the index name or if the specified index doesn't already exist on the
+ * cluster.
+ *
+ * @param index must not be {@literal null} or empty.
+ * @return new instance of {@link VectorSearchOperation.PathContributor}.
+ */
+ public static PathContributor search(String index) {
+ return new VectorSearchBuilder().index(index);
+ }
+
+ /**
+ * Configure the search type to use. {@link SearchType#ENN} leads to an exact search while {@link SearchType#ANN} uses
+ * {@code exact=false}.
+ *
+ * @param searchType must not be null.
+ * @return a new {@link VectorSearchOperation} with {@link SearchType} applied.
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation searchType(SearchType searchType) {
+ return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
+ scoreCriteria);
+ }
+
+ /**
+ * Criteria expression that compares an indexed field with a boolean, date, objectId, number (not decimals), string,
+ * or UUID to use as a pre-filter.
+ *
+ * Atlas Vector Search supports only the filters for the following MQL match expressions:
+ *
+ *
$gt
+ *
$lt
+ *
$gte
+ *
$lte
+ *
$eq
+ *
$ne
+ *
$in
+ *
$nin
+ *
$nor
+ *
$not
+ *
$and
+ *
$or
+ *
+ *
+ * @param filter must not be null.
+ * @return a new {@link VectorSearchOperation} with {@link CriteriaDefinition} applied.
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation filter(CriteriaDefinition filter) {
+ return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
+ scoreCriteria);
+ }
+
+ /**
+ * Criteria expression that compares an indexed field with a boolean, date, objectId, number (not decimals), string,
+ * or UUID to use as a pre-filter.
+ *
+ * Atlas Vector Search supports only the filters for the following MQL match expressions:
+ *
+ *
$gt
+ *
$lt
+ *
$gte
+ *
$lte
+ *
$eq
+ *
$ne
+ *
$in
+ *
$nin
+ *
$nor
+ *
$not
+ *
$and
+ *
$or
+ *
+ *
+ * @param filter must not be null.
+ * @return a new {@link VectorSearchOperation} with {@link CriteriaDefinition} applied.
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation filter(Document filter) {
+
+ return filter(new CriteriaDefinition() {
+ @Override
+ public Document getCriteriaObject() {
+ return filter;
+ }
+
+ @Nullable
+ @Override
+ public String getKey() {
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Number of nearest neighbors to use during the search. Value must be less than or equal to (<=) {@code 10000}. You
+ * can't specify a number less than the number of documents to return (limit). This field is required if
+ * {@link #searchType(SearchType)} is {@link SearchType#ANN} or {@link SearchType#DEFAULT}.
+ *
+ * @param numCandidates
+ * @return a new {@link VectorSearchOperation} with {@code numCandidates} applied.
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation numCandidates(int numCandidates) {
+ return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
+ scoreCriteria);
+ }
+
+ /**
+ * Add a {@link AddFieldsOperation} stage including the search score using {@code score} as field name.
+ *
+ * @return a new {@link VectorSearchOperation} with search score applied.
+ * @see #withSearchScore(String)
+ */
+ @Contract("-> new")
+ public VectorSearchOperation withSearchScore() {
+ return withSearchScore("score");
+ }
+
+ /**
+ * Add a {@link AddFieldsOperation} stage including the search score using {@code scoreFieldName} as field name.
+ *
+ * @param scoreFieldName name of the score field.
+ * @return a new {@link VectorSearchOperation} with {@code scoreFieldName} applied.
+ * @see #withSearchScore()
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation withSearchScore(String scoreFieldName) {
+ return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, scoreFieldName,
+ scoreCriteria);
+ }
+
+ /**
+ * Add a {@link MatchOperation} stage targeting the score field name. Implies that the score field is present by
+ * either reusing a previous {@link AddFieldsOperation} from {@link #withSearchScore()} or
+ * {@link #withSearchScore(String)} or by adding a new {@link AddFieldsOperation} stage.
+ *
+ * @return a new {@link VectorSearchOperation} with search score filter applied.
+ */
+ @Contract("_ -> new")
+ public VectorSearchOperation withFilterBySore(Consumer score) {
+ return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector,
+ StringUtils.hasText(this.score) ? this.score : "score", score);
+ }
+
+ @Override
+ public Document toDocument(AggregationOperationContext context) {
+
+ Document $vectorSearch = new Document();
+
+ if (searchType != null && !searchType.equals(SearchType.DEFAULT)) {
+ $vectorSearch.append("exact", searchType.equals(SearchType.ENN));
+ }
+
+ if (filter != null) {
+ $vectorSearch.append("filter", context.getMappedObject(filter.getCriteriaObject()));
+ }
+
+ $vectorSearch.append("index", indexName);
+ $vectorSearch.append("limit", limit.max());
+
+ if (numCandidates != null) {
+ $vectorSearch.append("numCandidates", numCandidates);
+ }
+
+ Object path = this.path.getPathObject();
+
+ if (path instanceof String pathFieldName) {
+ Document mappedObject = context.getMappedObject(new Document(pathFieldName, 1));
+ path = mappedObject.keySet().iterator().next();
+ }
+
+ Object source = vector.getSource();
+
+ if (source instanceof float[]) {
+ source = vector.toDoubleArray();
+ }
+
+ if (source instanceof double[] ds) {
+ source = Arrays.stream(ds).boxed().collect(Collectors.toList());
+ }
+
+ $vectorSearch.append("path", path);
+ $vectorSearch.append("queryVector", source);
+
+ return new Document(getOperator(), $vectorSearch);
+ }
+
+ @Override
+ public List toPipelineStages(AggregationOperationContext context) {
+
+ if (!StringUtils.hasText(score)) {
+ return List.of(toDocument(context));
+ }
+
+ AddFieldsOperation $vectorSearchScore = Aggregation.addFields().addField(score)
+ .withValueOfExpression("{\"$meta\":\"vectorSearchScore\"}").build();
+
+ if (scoreCriteria == null) {
+ return List.of(toDocument(context), $vectorSearchScore.toDocument(context));
+ }
+
+ Criteria criteria = Criteria.where(score);
+ scoreCriteria.accept(criteria);
+ MatchOperation $filterByScore = Aggregation.match(criteria);
+
+ return List.of(toDocument(context), $vectorSearchScore.toDocument(context), $filterByScore.toDocument(context));
+ }
+
+ @Override
+ public String getOperator() {
+ return "$vectorSearch";
+ }
+
+ /**
+ * Builder helper to create a {@link VectorSearchOperation}.
+ */
+ private static class VectorSearchBuilder implements PathContributor, VectorContributor, LimitContributor {
+
+ String index;
+ QueryPath paths;
+ Vector vector;
+
+ PathContributor index(String index) {
+ this.index = index;
+ return this;
+ }
+
+ @Override
+ public VectorContributor path(String path) {
+
+ this.paths = QueryPath.path(path);
+ return this;
+ }
+
+ @Override
+ public VectorSearchOperation limit(Limit limit) {
+ return new VectorSearchOperation(index, QueryPaths.of(paths), limit, vector);
+ }
+
+ @Override
+ public LimitContributor vector(Vector vector) {
+ this.vector = vector;
+ return this;
+ }
+ }
+
+ /**
+ * Search type, ANN as approximation or ENN for exact search.
+ */
public enum SearchType {
/** MongoDB Server default (value will be omitted) */
@@ -131,190 +426,102 @@ public class VectorSearchOperation implements AggregationOperation {
}
}
- private SearchType searchType;
- private CriteriaDefinition filter;
- private String indexName;
- private Limit limit;
- private Integer numCandidates;
- private QueryPaths path;
- private List vector;
-
- private String score;
- private Consumer scoreCriteria;
-
- private VectorSearchOperation(SearchType searchType, CriteriaDefinition filter, String indexName, Limit limit,
- Integer numCandidates, QueryPaths path, List vector, String searchScore,
- Consumer scoreCriteria) {
-
- this.searchType = searchType;
- this.filter = filter;
- this.indexName = indexName;
- this.limit = limit;
- this.numCandidates = numCandidates;
- this.path = path;
- this.vector = vector;
- this.score = searchScore;
- this.scoreCriteria = scoreCriteria;
- }
-
- public VectorSearchOperation(String indexName, QueryPaths path, Limit limit, List vector) {
- this(SearchType.DEFAULT, null, indexName, limit, null, path, vector, null, null);
- }
-
- static PathContributor search(String index) {
- return new VectorSearchBuilder().index(index);
- }
-
- public VectorSearchOperation(String indexName, String path, Limit limit, List vector) {
- this(indexName, QueryPaths.of(QueryPath.path(path)), limit, vector);
- }
-
- public VectorSearchOperation searchType(SearchType searchType) {
- return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
- scoreCriteria);
- }
-
- public VectorSearchOperation filter(Document filter) {
-
- return filter(new CriteriaDefinition() {
- @Override
- public Document getCriteriaObject() {
- return filter;
- }
-
- @Nullable
- @Override
- public String getKey() {
- return null;
- }
- });
- }
-
- public VectorSearchOperation filter(CriteriaDefinition filter) {
- return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
- scoreCriteria);
- }
-
- public VectorSearchOperation numCandidates(int numCandidates) {
- return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, score,
- scoreCriteria);
- }
-
- public VectorSearchOperation searchScore() {
- return searchScore("score");
- }
-
- public VectorSearchOperation searchScore(String scoreFieldName) {
- return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector, scoreFieldName,
- scoreCriteria);
- }
-
- public VectorSearchOperation filterBySore(Consumer score) {
- return new VectorSearchOperation(searchType, filter, indexName, limit, numCandidates, path, vector,
- StringUtils.hasText(this.score) ? this.score : "score", score);
- }
-
- @Override
- public Document toDocument(AggregationOperationContext context) {
-
- Document $vectorSearch = new Document();
-
- $vectorSearch.append("index", indexName);
- $vectorSearch.append("path", path.getPathObject());
- $vectorSearch.append("queryVector", vector);
- $vectorSearch.append("limit", limit.max());
-
- if (searchType != null && !searchType.equals(SearchType.DEFAULT)) {
- $vectorSearch.append("exact", searchType.equals(SearchType.ENN));
- }
-
- if (filter != null) {
- $vectorSearch.append("filter", context.getMappedObject(filter.getCriteriaObject()));
- }
-
- if (numCandidates != null) {
- $vectorSearch.append("numCandidates", numCandidates);
- }
-
- return new Document(getOperator(), $vectorSearch);
- }
-
- @Override
- public List toPipelineStages(AggregationOperationContext context) {
-
- if (!StringUtils.hasText(score)) {
- return List.of(toDocument(context));
- }
-
- AddFieldsOperation $vectorSearchScore = Aggregation.addFields().addField(score)
- .withValueOfExpression("{\"$meta\":\"vectorSearchScore\"}").build();
-
- if (scoreCriteria == null) {
- return List.of(toDocument(context), $vectorSearchScore.toDocument(context));
- }
-
- Criteria criteria = Criteria.where(score);
- scoreCriteria.accept(criteria);
- MatchOperation $filterByScore = Aggregation.match(criteria);
-
- return List.of(toDocument(context), $vectorSearchScore.toDocument(context), $filterByScore.toDocument(context));
- }
-
- @Override
- public String getOperator() {
- return "$vectorSearch";
- }
-
- public static class VectorSearchBuilder implements PathContributor, VectorContributor, LimitContributor {
-
- String index;
- QueryPaths paths;
- private List vector;
-
- PathContributor index(String index) {
- this.index = index;
- return this;
- }
-
- @Override
- public VectorContributor path(QueryPaths paths) {
- this.paths = paths;
- return this;
- }
-
- @Override
- public VectorSearchOperation limit(Limit limit) {
- return new VectorSearchOperation(index, paths, limit, vector);
- }
-
- @Override
- public LimitContributor vectors(List vectors) {
- this.vector = vectors;
- return this;
- }
- }
-
public interface PathContributor {
- default VectorContributor path(String path) {
- return path(QueryPaths.of(QueryPath.path(path)));
- }
- VectorContributor path(QueryPaths paths);
+ /**
+ * Indexed vector type field to search.
+ *
+ * @param path name of the search path.
+ * @return
+ */
+ @Contract("_ -> this")
+ VectorContributor path(String path);
}
public interface VectorContributor {
- default LimitContributor vectors(Double... vectors) {
- return vectors(Arrays.asList(vectors));
+
+ /**
+ * Array of float numbers that represent the query vector. The number type must match the indexed field value type.
+ * Otherwise, Atlas Vector Search doesn't return any results or errors.
+ *
+ * @param vector the query vector.
+ * @return
+ */
+ @Contract("_ -> this")
+ default LimitContributor vector(float... vector) {
+ return vector(Vector.of(vector));
}
- LimitContributor vectors(List vectors);
+ /**
+ * Array of double numbers that represent the query vector. The number type must match the indexed field value type.
+ * Otherwise, Atlas Vector Search doesn't return any results or errors.
+ *
+ * @param vector the query vector.
+ * @return
+ */
+ @Contract("_ -> this")
+ default LimitContributor vector(double... vector) {
+ return vector(Vector.of(vector));
+ }
+
+ /**
+ * Array of numbers that represent the query vector. The number type must match the indexed field value type.
+ * Otherwise, Atlas Vector Search doesn't return any results or errors.
+ *
+ * @param vector the query vector.
+ * @return
+ */
+ @Contract("_ -> this")
+ default LimitContributor vector(List extends Number> vector) {
+ return vector(Vector.of(vector));
+ }
+
+ /**
+ * Binary vector (BSON BinData vector subtype float32, or BSON BinData vector subtype int1 or int8 type) that
+ * represent the query vector. The number type must match the indexed field value type. Otherwise, Atlas Vector
+ * Search doesn't return any results or errors.
+ *
+ * @param vector the query vector.
+ * @return
+ */
+ @Contract("_ -> this")
+ default LimitContributor vector(BinaryVector vector) {
+ return vector(MongoVector.of(vector));
+ }
+
+ /**
+ * The query vector. The number type must match the indexed field value type. Otherwise, Atlas Vector Search doesn't
+ * return any results or errors.
+ *
+ * @param vector the query vector.
+ * @return
+ */
+ @Contract("_ -> this")
+ LimitContributor vector(Vector vector);
}
public interface LimitContributor {
+
+ /**
+ * Number (of type int only) of documents to return in the results. This value can't exceed the value of
+ * numCandidates if you specify numCandidates.
+ *
+ * @param limit
+ * @return
+ */
+ @Contract("_ -> this")
default VectorSearchOperation limit(int limit) {
return limit(Limit.of(limit));
}
+ /**
+ * Number (of type int only) of documents to return in the results. This value can't exceed the value of
+ * numCandidates if you specify numCandidates.
+ *
+ * @param limit
+ * @return
+ */
+ @Contract("_ -> this")
VectorSearchOperation limit(Limit limit);
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java
index 46dc22d99..d9f6ca43b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java
@@ -31,6 +31,9 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import org.bson.BinaryVector;
+import org.bson.BsonArray;
+import org.bson.BsonDouble;
import org.bson.BsonReader;
import org.bson.BsonTimestamp;
import org.bson.BsonUndefined;
@@ -44,6 +47,7 @@ import org.bson.types.Binary;
import org.bson.types.Code;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
+
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
@@ -51,7 +55,9 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
+import org.springframework.data.domain.Vector;
import org.springframework.data.mongodb.core.mapping.FieldName;
+import org.springframework.data.mongodb.core.mapping.MongoVector;
import org.springframework.data.mongodb.core.query.Term;
import org.springframework.data.mongodb.core.script.NamedMongoScript;
import org.springframework.util.Assert;
@@ -106,6 +112,10 @@ abstract class MongoConverters {
converters.add(BinaryToByteArrayConverter.INSTANCE);
converters.add(BsonTimestampToInstantConverter.INSTANCE);
+ converters.add(VectorToBsonArrayConverter.INSTANCE);
+ converters.add(ListToVectorConverter.INSTANCE);
+ converters.add(BinaryVectorToMongoVectorConverter.INSTANCE);
+
converters.add(reading(BsonUndefined.class, Object.class, it -> null));
converters.add(reading(String.class, URI.class, URI::create).andWriting(URI::toString));
@@ -417,6 +427,52 @@ abstract class MongoConverters {
}
}
+ @WritingConverter
+ enum VectorToBsonArrayConverter implements Converter {
+
+ INSTANCE;
+
+ @Override
+ public Object convert(Vector source) {
+
+ if (source instanceof MongoVector mv) {
+ return mv.getSource();
+ }
+
+ double[] doubleArray = source.toDoubleArray();
+
+ BsonArray array = new BsonArray(doubleArray.length);
+
+ for (double v : doubleArray) {
+ array.add(new BsonDouble(v));
+ }
+
+ return array;
+ }
+ }
+
+ @ReadingConverter
+ enum ListToVectorConverter implements Converter, Vector> {
+
+ INSTANCE;
+
+ @Override
+ public Vector convert(List source) {
+ return Vector.of(source);
+ }
+ }
+
+ @ReadingConverter
+ enum BinaryVectorToMongoVectorConverter implements Converter {
+
+ INSTANCE;
+
+ @Override
+ public Vector convert(BinaryVector source) {
+ return MongoVector.of(source);
+ }
+ }
+
/**
* {@link ConverterFactory} implementation converting {@link AtomicLong} into {@link Long}.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
index 39559b997..cce809adc 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
@@ -1130,7 +1130,7 @@ public class QueryMapper {
* @author Oliver Gierke
* @author Thomas Darimont
*/
- protected static class MetadataBackedField extends Field {
+ public static class MetadataBackedField extends Field {
private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?");
private static final Pattern NUMERIC_SEGMENT = Pattern.compile("\\d+");
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DefaultSearchIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DefaultSearchIndexOperations.java
index 1d323f333..e6a8778d7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DefaultSearchIndexOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DefaultSearchIndexOperations.java
@@ -18,67 +18,91 @@ package org.springframework.data.mongodb.core.index;
import java.util.ArrayList;
import java.util.List;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.bson.Document;
-import org.springframework.data.mongodb.core.DefaultIndexOperations;
+
+import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
-import org.springframework.data.mongodb.core.convert.QueryMapper;
-import org.springframework.data.mongodb.core.index.SearchIndex.Filter;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
-import org.springframework.lang.NonNull;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
+import com.mongodb.client.model.SearchIndexModel;
+import com.mongodb.client.model.SearchIndexType;
+
/**
* @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 3.5
*/
-public class DefaultSearchIndexOperations extends DefaultIndexOperations implements SearchIndexOperations {
+public class DefaultSearchIndexOperations implements SearchIndexOperations {
- private static final Log LOGGER = LogFactory.getLog(SearchIndexOperations.class);
+ private final MongoOperations mongoOperations;
+ private final String collectionName;
+ private final TypeInformation> entityTypeInformation;
public DefaultSearchIndexOperations(MongoOperations mongoOperations, Class> type) {
this(mongoOperations, mongoOperations.getCollectionName(type), type);
}
public DefaultSearchIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class> type) {
- super(mongoOperations, collectionName, type);
+ this.collectionName = collectionName;
+
+ if (type != null) {
+
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext = mongoOperations
+ .getConverter().getMappingContext();
+ entityTypeInformation = mappingContext.getRequiredPersistentEntity(type).getTypeInformation();
+ } else {
+ entityTypeInformation = null;
+ }
+
+ this.mongoOperations = mongoOperations;
}
- private static String getMappedPath(String path, MongoPersistentEntity> entity, QueryMapper mapper) {
- return mapper.getMappedFields(new Document(path, 1), entity).entrySet().iterator().next().getKey();
+ @Override
+ public String ensureIndex(SearchIndexDefinition indexDefinition) {
+
+ if (!(indexDefinition instanceof VectorIndex vsi)) {
+ throw new IllegalStateException("Index definitions must be of type VectorIndex");
+ }
+
+ Document index = indexDefinition.getIndexDocument(entityTypeInformation,
+ mongoOperations.getConverter().getMappingContext());
+
+ mongoOperations.getCollection(collectionName).createSearchIndexes(List
+ .of(new SearchIndexModel(vsi.getName(), (Document) index.get("definition"), SearchIndexType.vectorSearch())));
+
+ return vsi.getName();
+ }
+
+ @Override
+ public void updateIndex(SearchIndexDefinition index) {
+
+ if (index instanceof VectorIndex) {
+ throw new UnsupportedOperationException("Vector Index definitions cannot be updated");
+ }
+
+ Document indexDocument = index.getIndexDocument(entityTypeInformation,
+ mongoOperations.getConverter().getMappingContext());
+
+ mongoOperations.getCollection(collectionName).updateSearchIndex(index.getName(), indexDocument);
}
@Override
public boolean exists(String indexName) {
- // https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/
- AggregationResults aggregate = mongoOperations.aggregate(
- Aggregation.newAggregation(context -> new Document("$listSearchIndexes", new Document("name", indexName))),
- collectionName, Document.class);
+ List indexes = mongoOperations.getCollection(collectionName).listSearchIndexes().into(new ArrayList<>());
- return aggregate.iterator().hasNext();
- }
-
- @Override
- public void updateIndex(SearchIndex index) {
-
- MongoPersistentEntity> entity = lookupPersistentEntity(type, collectionName);
-
- Document indexDocument = createIndexDocument(index, entity);
-
- Document cmdResult = mongoOperations.execute(db -> {
-
- Document command = new Document().append("updateSearchIndex", collectionName).append("name", index.getName());
- command.putAll(indexDocument);
- command.remove("type");
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Updating VectorIndex: db.runCommand(%s)".formatted(command.toJson()));
+ for (Document index : indexes) {
+ if (index.getString("name").equals(indexName)) {
+ return true;
}
- return db.runCommand(command);
- });
+ }
+
+ return false;
}
@Override
@@ -106,59 +130,13 @@ public class DefaultSearchIndexOperations extends DefaultIndexOperations impleme
}
@Override
- public String ensureIndex(SearchIndexDefinition indexDefinition) {
-
- if (!(indexDefinition instanceof SearchIndex vsi)) {
- throw new IllegalStateException("Index definitions must be of type VectorIndex");
- }
-
- MongoPersistentEntity> entity = lookupPersistentEntity(type, collectionName);
-
- Document index = createIndexDocument(vsi, entity);
-
- Document cmdResult = mongoOperations.execute(db -> {
-
- Document command = new Document().append("createSearchIndexes", collectionName).append("indexes", List.of(index));
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Creating VectorIndex: db.runCommand(%s)".formatted(command.toJson()));
- }
- return db.runCommand(command);
- });
-
- return cmdResult.get("ok").toString().equalsIgnoreCase("1.0") ? vsi.getName() : cmdResult.toJson();
- }
-
- @NonNull
- private Document createIndexDocument(SearchIndex vsi, MongoPersistentEntity> entity) {
-
- Document index = new Document(vsi.getIndexOptions());
- Document definition = new Document();
-
- List fields = new ArrayList<>(vsi.getFilters().size() + 1);
-
- Document vectorField = new Document("type", "vector");
- vectorField.append("path", getMappedPath(vsi.getPath(), entity, mapper));
- vectorField.append("numDimensions", vsi.getDimensions());
- vectorField.append("similarity", vsi.getSimilarity());
-
- fields.add(vectorField);
-
- for (Filter filter : vsi.getFilters()) {
- fields.add(new Document("type", "filter").append("path", getMappedPath(filter.path(), entity, mapper)));
- }
-
- definition.append("fields", fields);
- index.append("definition", definition);
- return index;
+ public void dropAllIndexes() {
+ getIndexInfo().forEach(indexInfo -> dropIndex(indexInfo.getName()));
}
@Override
public void dropIndex(String name) {
-
- Document command = new Document().append("dropSearchIndex", collectionName).append("name", name);
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Dropping VectorIndex: db.runCommand(%s)".formatted(command.toJson()));
- }
- mongoOperations.execute(db -> db.runCommand(command));
+ mongoOperations.getCollection(collectionName).dropSearchIndex(name);
}
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java
index d86d90e3f..ca3d951c9 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java
@@ -18,7 +18,7 @@ package org.springframework.data.mongodb.core.index;
import org.springframework.lang.Nullable;
/**
- * Provider interface to obtain {@link IndexOperations} by MongoDB collection name.
+ * Provider interface to obtain {@link IndexOperations} by MongoDB collection name or entity type.
*
* @author Mark Paluch
* @author Jens Schauder
@@ -46,4 +46,5 @@ public interface IndexOperationsProvider {
* @since 3.2
*/
IndexOperations indexOps(String collectionName, @Nullable Class> type);
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndex.java
deleted file mode 100644
index ddb61da7e..000000000
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndex.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright 2024. 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
- *
- * http://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.
- */
-
-/*
- * Copyright 2024 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
- *
- * http://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.mongodb.core.index;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.bson.Document;
-
-/**
- * {@link IndexDefinition} for creating MongoDB
- * Vector Index required to
- * run {@code $vectorSearch} queries.
- *
- * @author Christoph Strobl
- */
-public class SearchIndex implements SearchIndexDefinition {
-
- private final String name;
- private String path;
- private int dimensions;
- private String similarity;
- private List filters;
- private String quantization = Quantization.NONE.name();
-
- /**
- * Create a new {@link SearchIndex} instance.
- *
- * @param name The name of the index.
- */
- public SearchIndex(String name) {
- this.name = name;
- }
-
- /**
- * Create a new {@link SearchIndex} instance using similarity based on the angle between vectors.
- *
- * @param name The name of the index.
- * @return new instance of {@link SearchIndex}.
- */
- public static SearchIndex cosine(String name) {
-
- SearchIndex idx = new SearchIndex(name);
- return idx.similarity(SimilarityFunction.COSINE);
- }
-
- /**
- * Create a new {@link SearchIndex} instance using similarity based the distance between vector ends.
- *
- * @param name The name of the index.
- * @return new instance of {@link SearchIndex}.
- */
- public static SearchIndex euclidean(String name) {
-
- SearchIndex idx = new SearchIndex(name);
- return idx.similarity(SimilarityFunction.EUCLIDEAN);
- }
-
- /**
- * Create a new {@link SearchIndex} instance using similarity based on based on both angle and magnitude of the
- * vectors.
- *
- * @param name The name of the index.
- * @return new instance of {@link SearchIndex}.
- */
- public static SearchIndex dotProduct(String name) {
-
- SearchIndex idx = new SearchIndex(name);
- return idx.similarity(SimilarityFunction.DOT_PRODUCT);
- }
-
- /**
- * The path to the field/property to index.
- *
- * @param path The path using dot notation.
- * @return this.
- */
- public SearchIndex path(String path) {
-
- this.path = path;
- return this;
- }
-
- /**
- * Number of vector dimensions enforced at index- & query-time.
- *
- * @param dimensions value between {@code 0} and {@code 4096}.
- * @return this.
- */
- public SearchIndex dimensions(int dimensions) {
- this.dimensions = dimensions;
- return this;
- }
-
- /**
- * Similarity function used.
- *
- * @param similarity should be one of {@literal euclidean | cosine | dotProduct}.
- * @return this.
- * @see SimilarityFunction
- * @see #similarity(SimilarityFunction)
- */
- public SearchIndex similarity(String similarity) {
- this.similarity = similarity;
- return this;
- }
-
- /**
- * Similarity function used.
- *
- * @param similarity must not be {@literal null}.
- * @return this.
- */
- public SearchIndex similarity(SimilarityFunction similarity) {
- return similarity(similarity.getFunctionName());
- }
-
-
- /**
- * Quantization used.
- *
- * @param quantization should be one of {@literal none | scalar | binary}.
- * @return this.
- * @see Quantization
- * @see #quantization(Quantization)
- */
- public SearchIndex quantization(String quantization) {
- this.quantization = quantization;
- return this;
- }
-
- /**
- * Quntization used.
- *
- * @param quantization must not be {@literal null}.
- * @return this.
- */
- public SearchIndex quantization(Quantization quantization) {
- return similarity(quantization.getQuantizationName());
- }
-
- /**
- * Add a {@link Filter} that can be used to narrow search scope.
- *
- * @param filter must not be {@literal null}.
- * @return this.
- */
- public SearchIndex filter(Filter filter) {
-
- if (this.filters == null) {
- this.filters = new ArrayList<>(3);
- }
-
- this.filters.add(filter);
- return this;
- }
-
- /**
- * Add a field that can be used to pre filter data.
- *
- * @param path Dot notation to field/property used for filtering.
- * @return this.
- * @see #filter(Filter)
- */
- public SearchIndex filter(String path) {
- return filter(new Filter(path));
- }
-
- @Override
- public Document getIndexOptions() {
- return new Document("name", name).append("type", "vectorSearch");
- }
-
- public String getName() {
- return name;
- }
-
- public String getPath() {
- return path;
- }
-
- public int getDimensions() {
- return dimensions;
- }
-
- public String getSimilarity() {
- return similarity;
- }
-
- public List getFilters() {
- return filters == null ? Collections.emptyList() : filters;
- }
-
- public record Filter(String path) {
-
- }
-
- public enum SimilarityFunction {
- DOT_PRODUCT("dotProduct"), COSINE("cosine"), EUCLIDEAN("euclidean");
-
- String functionName;
-
- SimilarityFunction(String functionName) {
- this.functionName = functionName;
- }
-
- public String getFunctionName() {
- return functionName;
- }
- }
-
- public enum Quantization {
- NONE("none"), SCALAR("scalar"), BINARY("binary");
-
- String quantizationName;
-
- Quantization(String quantizationName) {
- this.quantizationName = quantizationName;
- }
-
- public String getQuantizationName() {
- return quantizationName;
- }
- }
-}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java
index 5c03240c7..05db5e4ed 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java
@@ -17,15 +17,60 @@ package org.springframework.data.mongodb.core.index;
import org.bson.Document;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
/**
+ * Definition for an Atlas Search Index (Search Index or Vector Index).
+ *
* @author Marcin Grzejszczak
+ * @author Mark Paluch
+ * @since 4.5
*/
public interface SearchIndexDefinition {
/**
- * Get the index properties such as {@literal unique},...
- *
- * @return never {@literal null}.
+ * @return the name of the index.
*/
- Document getIndexOptions();
+ String getName();
+
+ /**
+ * @return the type of the index. Typically, {@code search} or {@code vectorSearch}.
+ */
+ String getType();
+
+ /**
+ * Returns the index document for this index in the context of a potential entity to resolve field name mappings. The
+ * resulting document contains the index name, type and {@link #getDefinition(TypeInformation, MappingContext)
+ * definition}.
+ *
+ * @param entity
+ * @param mappingContext
+ * @return
+ */
+ default Document getIndexDocument(@Nullable TypeInformation> entity,
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext) {
+
+ Document document = new Document();
+ document.put("name", getName());
+ document.put("type", getType());
+ document.put("definition", getDefinition(entity, mappingContext));
+
+ return document;
+ }
+
+ /**
+ * Returns the actual index definition for this index in the context of a potential entity to resolve field name
+ * mappings.
+ *
+ * @param entity
+ * @param mappingContext
+ * @return
+ */
+ Document getDefinition(@Nullable TypeInformation> entity,
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext);
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperations.java
index 417d31f36..24b7bc1f3 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperations.java
@@ -18,18 +18,53 @@ package org.springframework.data.mongodb.core.index;
import java.util.List;
/**
+ * Search Index operations on a collection for Atlas Search.
+ *
* @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 4.5
*/
public interface SearchIndexOperations {
+ /**
+ * Ensure that an index for the provided {@link SearchIndexDefinition} exists for the collection indicated by the
+ * entity class. If not it will be created.
+ *
+ * @param indexDefinition must not be {@literal null}.
+ * @return the index name.
+ */
String ensureIndex(SearchIndexDefinition indexDefinition);
- void updateIndex(SearchIndex index);
+ /**
+ * Alters the search {@code index}.
+ *
+ * Note that Atlas Search does not support updating Vector Search Indices resulting in
+ * {@link UnsupportedOperationException}.
+ *
+ * @param index the index definition.
+ */
+ void updateIndex(SearchIndexDefinition index);
- boolean exists(String indexName);
+ /**
+ * Check whether an index with the {@code name} exists.
+ *
+ * @param name name of index to check for presence.
+ * @return {@literal true} if the index exists; {@literal false} otherwise.
+ */
+ boolean exists(String name);
+ /**
+ * Drops an index from this collection.
+ *
+ * @param name name of index to drop.
+ */
void dropIndex(String name);
+ /**
+ * Drops all search indices from this collection.
+ */
+ void dropAllIndexes();
+
/**
* Returns the index information on the collection.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperationsProvider.java
index 9c20e982f..389b666a2 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperationsProvider.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperationsProvider.java
@@ -16,13 +16,36 @@
package org.springframework.data.mongodb.core.index;
/**
+ * Provider interface to obtain {@link SearchIndexOperations} by MongoDB collection name or entity type.
+ *
* @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 4.5
*/
public interface SearchIndexOperationsProvider {
- SearchIndexOperations searchIndexOps(String collectionName);
+ /**
+ * Returns the operations that can be performed on search indexes.
+ *
+ * @param collectionName name of the MongoDB collection, must not be {@literal null}.
+ * @return index operations on the named collection
+ */
+ SearchIndexOperations searchIndexOps(String collectionName);
- SearchIndexOperations searchIndexOps(Class> type);
+ /**
+ * Returns the operations that can be performed on search indexes.
+ *
+ * @param type the type used for field mapping.
+ * @return index operations on the named collection
+ */
+ SearchIndexOperations searchIndexOps(Class> type);
- SearchIndexOperations searchIndexOps(Class> type, String collectionName);
+ /**
+ * Returns the operations that can be performed on search indexes.
+ *
+ * @param collectionName name of the MongoDB collection, must not be {@literal null}.
+ * @param type the type used for field mapping. Can be {@literal null}.
+ * @return index operations on the named collection
+ */
+ SearchIndexOperations searchIndexOps(Class> type, String collectionName);
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/VectorIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/VectorIndex.java
new file mode 100644
index 000000000..9c5698985
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/VectorIndex.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2024. 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
+ *
+ * http://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.
+ */
+
+/*
+ * Copyright 2024 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
+ *
+ * http://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.mongodb.core.index;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.bson.Document;
+
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Contract;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * {@link IndexDefinition} for creating MongoDB
+ * Vector Index required to
+ * run {@code $vectorSearch} queries.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 4.5
+ */
+public class VectorIndex implements SearchIndexDefinition {
+
+ private final String name;
+ private final List