indexFields = new ArrayList<>();
+ String name = doc.getString("name");
+ for (Object field : doc.get("latestDefinition", Document.class).get("fields", List.class)) {
+
+ if (field instanceof Document fieldInfo) {
+ indexFields.add(IndexField.vector(fieldInfo.getString("path")));
+ }
+ }
+
+ result.add(new IndexInfo(indexFields, name, false, false, null, false));
+ }
+ return result;
+ }
+
+ @Override
+ public void dropAllIndexes() {
+ getIndexInfo().forEach(indexInfo -> dropIndex(indexInfo.getName()));
+ }
+
+ @Override
+ public void dropIndex(String name) {
+ mongoOperations.getCollection(collectionName).dropSearchIndex(name);
+ }
+
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java
index 3fff86a3ea..a5cbf6c896 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java
@@ -39,7 +39,12 @@ enum Type {
/**
* @since 3.3
*/
- WILDCARD
+ WILDCARD,
+
+ /**
+ * @since ?.?
+ */
+ VECTOR
}
private final String key;
@@ -58,7 +63,7 @@ private IndexField(String key, @Nullable Direction direction, @Nullable Type typ
if (Type.GEO.equals(type) || Type.TEXT.equals(type)) {
Assert.isNull(direction, "Geo/Text indexes must not have a direction");
} else {
- if (!(Type.HASH.equals(type) || Type.WILDCARD.equals(type))) {
+ if (!(Type.HASH.equals(type) || Type.WILDCARD.equals(type) || Type.VECTOR.equals(type))) {
Assert.notNull(direction, "Default indexes require a direction");
}
}
@@ -76,6 +81,10 @@ public static IndexField create(String key, Direction order) {
return new IndexField(key, order, Type.DEFAULT);
}
+ public static IndexField vector(String key) {
+ return new IndexField(key, null, Type.VECTOR);
+ }
+
/**
* Creates a {@literal hashed} {@link IndexField} for the given key.
*
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 d86d90e3f6..ca3d951c94 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 @@
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 @@ default IndexOperations indexOps(String collectionName) {
* @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/SearchIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java
new file mode 100644
index 0000000000..05db5e4edc
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexDefinition.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011-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
+ *
+ * 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.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 {
+
+ /**
+ * @return the name of the index.
+ */
+ 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
new file mode 100644
index 0000000000..24b7bc1f30
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperations.java
@@ -0,0 +1,74 @@
+/*
+ * 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.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);
+
+ /**
+ * 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);
+
+ /**
+ * 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.
+ *
+ * @return index information on the collection
+ */
+ List getIndexInfo();
+}
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
new file mode 100644
index 0000000000..389b666a23
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/SearchIndexOperationsProvider.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * 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 {
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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 0000000000..9c56989856
--- /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 fields = new ArrayList<>();
+
+ /**
+ * Create a new {@link VectorIndex} instance.
+ *
+ * @param name The name of the index.
+ */
+ public VectorIndex(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Add a filter field.
+ *
+ * @param path dot notation to field/property used for filtering.
+ * @return this.
+ */
+ @Contract("_ -> this")
+ public VectorIndex addFilter(String path) {
+
+ Assert.hasText(path, "Path must not be null or empty");
+
+ fields.add(new VectorFilterField(path, "filter"));
+ return this;
+ }
+
+ /**
+ * Add a vector field and accept a {@link VectorFieldBuilder} customizer.
+ *
+ * @param path dot notation to field/property used for filtering.
+ * @param customizer customizer function.
+ * @return this.
+ */
+ @Contract("_, _ -> this")
+ public VectorIndex addVector(String path, Consumer customizer) {
+
+ Assert.hasText(path, "Path must not be null or empty");
+
+ VectorFieldBuilder builder = new VectorFieldBuilder(path, "vector");
+ customizer.accept(builder);
+
+ fields.add(
+ new VectorIndexField(builder.path, builder.type, builder.dimensions, builder.similarity, builder.quantization));
+
+ return this;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getType() {
+ return "vectorSearch";
+ }
+
+ @Override
+ public Document getDefinition(@Nullable TypeInformation> entity,
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext) {
+
+ if (fields.isEmpty()) {
+ throw new IllegalStateException("At least one vector or filter field must be added to the index");
+ }
+
+ MongoPersistentEntity> persistentEntity = entity != null ? mappingContext.getPersistentEntity(entity) : null;
+
+ Document definition = new Document();
+ List fields = new ArrayList<>();
+ definition.put("fields", fields);
+
+ for (Object field : this.fields) {
+
+ if (field instanceof VectorFilterField vff) {
+
+ Document filter = new Document("type", "filter");
+ filter.put("path", resolvePath(vff.path(), persistentEntity, mappingContext));
+ fields.add(filter);
+ }
+
+ if (field instanceof VectorIndexField vif) {
+
+ Document filter = new Document("type", "vector");
+ filter.put("path", resolvePath(vif.path(), persistentEntity, mappingContext));
+ filter.put("numDimensions", vif.dimensions());
+ filter.put("similarity", vif.similarity());
+ filter.put("quantization", vif.quantization());
+ fields.add(filter);
+ }
+
+ }
+
+ return definition;
+ }
+
+ private String resolvePath(String path, @Nullable MongoPersistentEntity> persistentEntity,
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext) {
+
+ if (persistentEntity == null) {
+ return path;
+ }
+
+ QueryMapper.MetadataBackedField mbf = new QueryMapper.MetadataBackedField(path, persistentEntity, mappingContext);
+
+ return mbf.getMappedKey();
+ }
+
+ record VectorIndexField(String path, String type, int dimensions, String similarity, String quantization) {
+ }
+
+ record VectorFilterField(String path, String type) {
+ }
+
+ public static class VectorFieldBuilder {
+
+ private final String path;
+ private final String type;
+
+ private int dimensions;
+ private @Nullable String similarity;
+ private String quantization = "none";
+
+ VectorFieldBuilder(String path, String type) {
+ this.path = path;
+ this.type = type;
+ }
+
+ /**
+ * Number of vector dimensions enforced at index- & query-time.
+ *
+ * @param dimensions value between {@code 0} and {@code 4096}.
+ * @return this.
+ */
+ @Contract("_ -> this")
+ public VectorFieldBuilder dimensions(int dimensions) {
+ this.dimensions = dimensions;
+ return this;
+ }
+
+ /**
+ * Use similarity based on the angle between vectors.
+ *
+ * @return new instance of {@link VectorIndex}.
+ */
+ @Contract(" -> this")
+ public VectorFieldBuilder cosine() {
+
+ return similarity(SimilarityFunction.COSINE);
+ }
+
+ /**
+ * Use similarity based the distance between vector ends.
+ */
+ @Contract(" -> this")
+ public VectorFieldBuilder euclidean() {
+ return similarity(SimilarityFunction.EUCLIDEAN);
+ }
+
+ /**
+ * Use similarity based on both angle and magnitude of the vectors.
+ *
+ * @param name The name of the index.
+ * @return new instance of {@link VectorIndex}.
+ */
+ @Contract(" -> this")
+ public VectorFieldBuilder dotProduct() {
+ return similarity(SimilarityFunction.DOT_PRODUCT);
+ }
+
+ /**
+ * Similarity function used.
+ *
+ * @param similarity should be one of {@literal euclidean | cosine | dotProduct}.
+ * @return this.
+ * @see SimilarityFunction
+ * @see #similarity(SimilarityFunction)
+ */
+ @Contract("_ -> this")
+ public VectorFieldBuilder similarity(String similarity) {
+ this.similarity = similarity;
+ return this;
+ }
+
+ /**
+ * Similarity function used.
+ *
+ * @param similarity must not be {@literal null}.
+ * @return this.
+ */
+ @Contract("_ -> this")
+ public VectorFieldBuilder 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 VectorFieldBuilder quantization(String quantization) {
+ this.quantization = quantization;
+ return this;
+ }
+
+ /**
+ * Quntization used.
+ *
+ * @param quantization must not be {@literal null}.
+ * @return this.
+ */
+ public VectorFieldBuilder quantization(Quantization quantization) {
+ return quantization(quantization.getQuantizationName());
+ }
+ }
+
+ public enum SimilarityFunction {
+ DOT_PRODUCT("dotProduct"), COSINE("cosine"), EUCLIDEAN("euclidean");
+
+ final String functionName;
+
+ SimilarityFunction(String functionName) {
+ this.functionName = functionName;
+ }
+
+ public String getFunctionName() {
+ return functionName;
+ }
+ }
+
+ public enum Quantization {
+ NONE("none"), SCALAR("scalar"), BINARY("binary");
+
+ final 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/mapping/MongoSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java
index 062b006c34..3b3a520bc3 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java
@@ -53,13 +53,13 @@ public abstract class MongoSimpleTypes {
public static final Set> AUTOGENERATED_ID_TYPES = Set.of(ObjectId.class, String.class, BigInteger.class);
private static final Set> MONGO_SIMPLE_TYPES = Set.of(Binary.class, DBRef.class, Decimal128.class,
org.bson.Document.class, Code.class, CodeWScope.class, CodeWithScope.class, ObjectId.class, Pattern.class,
- Symbol.class, UUID.class, Instant.class, BsonValue.class, BsonNumber.class, BsonType.class, BsonArray.class,
- BsonSymbol.class, BsonUndefined.class, BsonMinKey.class, BsonMaxKey.class, BsonNull.class, BsonBinary.class,
- BsonBoolean.class, BsonDateTime.class, BsonDbPointer.class, BsonDecimal128.class, BsonDocument.class,
- BsonDouble.class, BsonInt32.class, BsonInt64.class, BsonJavaScript.class, BsonJavaScriptWithScope.class,
- BsonObjectId.class, BsonRegularExpression.class, BsonString.class, BsonTimestamp.class, Geometry.class,
- GeometryCollection.class, LineString.class, MultiLineString.class, MultiPoint.class, MultiPolygon.class,
- Point.class, Polygon.class);
+ Symbol.class, UUID.class, Instant.class, BinaryVector.class, BsonValue.class, BsonNumber.class, BsonType.class,
+ BsonArray.class, BsonSymbol.class, BsonUndefined.class, BsonMinKey.class, BsonMaxKey.class, BsonNull.class,
+ BsonBinary.class, BsonBoolean.class, BsonDateTime.class, BsonDbPointer.class, BsonDecimal128.class,
+ BsonDocument.class, BsonDouble.class, BsonInt32.class, BsonInt64.class, BsonJavaScript.class,
+ BsonJavaScriptWithScope.class, BsonObjectId.class, BsonRegularExpression.class, BsonString.class,
+ BsonTimestamp.class, Geometry.class, GeometryCollection.class, LineString.class, MultiLineString.class,
+ MultiPoint.class, MultiPolygon.class, Point.class, Polygon.class);
public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(MONGO_SIMPLE_TYPES, true) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoVector.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoVector.java
new file mode 100644
index 0000000000..63ca1d5d9c
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoVector.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2025 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.mongodb.core.mapping;
+
+import org.bson.BinaryVector;
+import org.bson.Float32BinaryVector;
+import org.bson.Int8BinaryVector;
+import org.bson.PackedBitBinaryVector;
+
+import org.springframework.data.domain.Vector;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * MongoDB-specific extension to {@link Vector} based on Mongo's {@link Binary}. Note that only float32 and int8
+ * variants can be represented as floating-point numbers. int1 returns an all-zero array for {@link #toFloatArray()} and
+ * {@link #toDoubleArray()}.
+ *
+ * @author Mark Paluch
+ * @since 4.5
+ */
+public class MongoVector implements Vector {
+
+ private final BinaryVector v;
+
+ MongoVector(BinaryVector v) {
+ this.v = v;
+ }
+
+ /**
+ * Creates a new {@link MongoVector} from the given {@link BinaryVector}.
+ *
+ * @param v binary vector representation.
+ * @return the {@link MongoVector} for the given vector values.
+ */
+ public static MongoVector of(BinaryVector v) {
+ return new MongoVector(v);
+ }
+
+ @Override
+ public Class extends Number> getType() {
+
+ if (v instanceof Float32BinaryVector) {
+ return Float.class;
+ }
+
+ if (v instanceof Int8BinaryVector) {
+ return Byte.class;
+ }
+
+ if (v instanceof PackedBitBinaryVector) {
+ return Byte.class;
+ }
+
+ return Number.class;
+ }
+
+ @Override
+ public BinaryVector getSource() {
+ return v;
+ }
+
+ @Override
+ public int size() {
+
+ if (v instanceof Float32BinaryVector f) {
+ return f.getData().length;
+ }
+
+ if (v instanceof Int8BinaryVector i) {
+ return i.getData().length;
+ }
+
+ if (v instanceof PackedBitBinaryVector p) {
+ return p.getData().length;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public float[] toFloatArray() {
+
+ if (v instanceof Float32BinaryVector f) {
+
+ float[] result = new float[f.getData().length];
+ System.arraycopy(f.getData(), 0, result, 0, result.length);
+ return result;
+ }
+
+ if (v instanceof Int8BinaryVector i) {
+
+ float[] result = new float[i.getData().length];
+ System.arraycopy(i.getData(), 0, result, 0, result.length);
+ return result;
+ }
+
+ return new float[size()];
+ }
+
+ @Override
+ public double[] toDoubleArray() {
+
+ if (v instanceof Float32BinaryVector f) {
+
+ float[] data = f.getData();
+ double[] result = new double[data.length];
+ for (int i = 0; i < data.length; i++) {
+ result[i] = data[i];
+ }
+
+ return result;
+ }
+
+ if (v instanceof Int8BinaryVector i) {
+
+ double[] result = new double[i.getData().length];
+ System.arraycopy(i.getData(), 0, result, 0, result.length);
+ return result;
+ }
+
+ return new double[size()];
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof MongoVector that)) {
+ return false;
+ }
+ return ObjectUtils.nullSafeEquals(v, that.v);
+ }
+
+ @Override
+ public int hashCode() {
+ return ObjectUtils.nullSafeHashCode(v);
+ }
+
+ @Override
+ public String toString() {
+ return "MV[" + v + "]";
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/aggregation/TestAggregationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/aggregation/TestAggregationContext.java
index e3be346039..344244717e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/aggregation/TestAggregationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/aggregation/TestAggregationContext.java
@@ -59,6 +59,11 @@ public static AggregationOperationContext contextFor(@Nullable Class> type, Mo
new QueryMapper(mongoConverter)).continueOnMissingFieldReference());
}
+ @Override
+ public Document getMappedObject(Document document) {
+ return delegate.getMappedObject(document);
+ }
+
@Override
public Document getMappedObject(Document document, @Nullable Class> type) {
return delegate.getMappedObject(document, type);
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperationUnitTests.java
new file mode 100644
index 0000000000..69348290f6
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchOperationUnitTests.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ *
+ * 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.mongodb.core.aggregation;
+
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.bson.Document;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.aggregation.VectorSearchOperation.SearchType;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.util.aggregation.TestAggregationContext;
+
+/**
+ * @author Christoph Strobl
+ */
+class VectorSearchOperationUnitTests {
+
+ static final Document $VECTOR_SEARCH = Document.parse(
+ "{'index' : 'vector_index', 'limit' : 10, 'path' : 'plot_embedding', 'queryVector' : [-0.0016261312, -0.028070757, -0.011342932]}");
+ static final VectorSearchOperation SEARCH_OPERATION = VectorSearchOperation.search("vector_index")
+ .path("plot_embedding").vector(-0.0016261312, -0.028070757, -0.011342932).limit(10);
+
+ @Test // GH-4706
+ void requiredArgs() {
+
+ List stages = SEARCH_OPERATION.toPipelineStages(Aggregation.DEFAULT_CONTEXT);
+ Assertions.assertThat(stages).containsExactly(new Document("$vectorSearch", $VECTOR_SEARCH));
+ }
+
+ @Test // GH-4706
+ void optionalArgs() {
+
+ VectorSearchOperation $search = SEARCH_OPERATION.numCandidates(150).searchType(SearchType.ENN)
+ .filter(new Criteria().andOperator(Criteria.where("year").gt(1955), Criteria.where("year").lt(1975)));
+
+ List stages = $search.toPipelineStages(Aggregation.DEFAULT_CONTEXT);
+
+ Document filter = new Document("$and",
+ List.of(new Document("year", new Document("$gt", 1955)), new Document("year", new Document("$lt", 1975))));
+ Assertions.assertThat(stages).containsExactly(new Document("$vectorSearch",
+ new Document($VECTOR_SEARCH).append("exact", true).append("filter", filter).append("numCandidates", 150)));
+ }
+
+ @Test // GH-4706
+ void withScore() {
+
+ List stages = SEARCH_OPERATION.withSearchScore().toPipelineStages(Aggregation.DEFAULT_CONTEXT);
+ Assertions.assertThat(stages).containsExactly(new Document("$vectorSearch", $VECTOR_SEARCH),
+ new Document("$addFields", new Document("score", new Document("$meta", "vectorSearchScore"))));
+ }
+
+ @Test // GH-4706
+ void withScoreFilter() {
+
+ List stages = SEARCH_OPERATION.withFilterBySore(score -> score.gt(50))
+ .toPipelineStages(Aggregation.DEFAULT_CONTEXT);
+ Assertions.assertThat(stages).containsExactly(new Document("$vectorSearch", $VECTOR_SEARCH),
+ new Document("$addFields", new Document("score", new Document("$meta", "vectorSearchScore"))),
+ new Document("$match", new Document("score", new Document("$gt", 50))));
+ }
+
+ @Test // GH-4706
+ void withScoreFilterOnCustomFieldName() {
+
+ List stages = SEARCH_OPERATION.withFilterBySore(score -> score.gt(50)).withSearchScore("s-c-o-r-e")
+ .toPipelineStages(Aggregation.DEFAULT_CONTEXT);
+ Assertions.assertThat(stages).containsExactly(new Document("$vectorSearch", $VECTOR_SEARCH),
+ new Document("$addFields", new Document("s-c-o-r-e", new Document("$meta", "vectorSearchScore"))),
+ new Document("$match", new Document("s-c-o-r-e", new Document("$gt", 50))));
+ }
+
+ @Test // GH-4706
+ void mapsCriteriaToDomainType() {
+
+ VectorSearchOperation $search = SEARCH_OPERATION
+ .filter(new Criteria().andOperator(Criteria.where("y").gt(1955), Criteria.where("y").lt(1975)));
+
+ List stages = $search.toPipelineStages(TestAggregationContext.contextFor(Movie.class));
+
+ Document filter = new Document("$and",
+ List.of(new Document("year", new Document("$gt", 1955)), new Document("year", new Document("$lt", 1975))));
+ Assertions.assertThat(stages)
+ .containsExactly(new Document("$vectorSearch", new Document($VECTOR_SEARCH).append("filter", filter)));
+ }
+
+ static class Movie {
+
+ @Id String id;
+ String title;
+
+ @Field("year") String y;
+ }
+
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchTests.java
new file mode 100644
index 0000000000..04859072d7
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/VectorSearchTests.java
@@ -0,0 +1,275 @@
+/*
+ * 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
+ *
+ * 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.mongodb.core.aggregation;
+
+import org.bson.Document;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.data.mongodb.core.index.VectorIndex;
+import org.springframework.data.mongodb.test.util.EnableIfVectorSearchAvailable;
+import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
+import org.springframework.data.mongodb.test.util.MongoTestTemplate;
+import org.springframework.data.mongodb.test.util.Template;
+
+/**
+ * @author Christoph Strobl
+ */
+@EnableIfVectorSearchAvailable
+@ExtendWith(MongoTemplateExtension.class)
+public class VectorSearchTests {
+
+ static final String COLLECTION_NAME = "movies";
+
+ @Template(database = "mflix") //
+ static MongoTestTemplate template;
+
+ @Test
+ void xxx() {
+
+ // boolean hasIndex = template.indexOps(COLLECTION_NAME).getIndexInfo().stream()
+ // .anyMatch(it -> it.getName().endsWith("movie_vector_index"));
+
+ // TODO: index conversion etc. is missing - should we combine the index info listing?
+ // boolean hasIndex = template.execute(db -> {
+ //
+ // Document doc = db.runCommand(new Document("listSearchIndexes", COLLECTION_NAME));
+ // Object searchIndexes = BsonUtils.resolveValue(BsonUtils.asMap(doc), "cursor.firstBatch");
+ // if(searchIndexes instanceof Collection> indexes) {
+ // return indexes.stream().anyMatch(it -> it instanceof Document idx && idx.get("name",
+ // String.class).equalsIgnoreCase("vector_index"));
+ // }
+ // return false;
+ // });
+
+ if (!template.collectionExists(COLLECTION_NAME)) {
+ template.createCollection(COLLECTION_NAME);
+ }
+
+ boolean hasIndex = template.searchIndexOps(COLLECTION_NAME).exists("movie_vector_index");
+
+ if (!hasIndex) {
+
+ System.out.print("Creating index: ");
+ VectorIndex vectorIndex = new VectorIndex("movie_vector_index").addVector("plot_embedding",
+ field -> field.dimensions(1536).similarity(VectorIndex.SimilarityFunction.COSINE)).addFilter("language");
+ String s = template.searchIndexOps(COLLECTION_NAME).ensureIndex(vectorIndex);
+ }
+
+ VectorSearchOperation $vectorSearch = VectorSearchOperation.search("movie_vector_index").path("plot_embedding")
+ .vector(vectors).limit(10).numCandidates(150).withSearchScore();
+
+ Aggregation agg = Aggregation.newAggregation($vectorSearch, Aggregation.project("plot", "title"));
+
+ AggregationResults aggregate = template.aggregate(agg, COLLECTION_NAME, Document.class);
+
+ aggregate.forEach(System.out::println);
+ }
+
+ static double[] vectors = { -0.0016261312, -0.028070757, -0.011342932, -0.012775794, -0.0027440966, 0.008683807,
+ -0.02575152, -0.02020668, -0.010283281, -0.0041719596, 0.021392956, 0.028657231, -0.006634482, 0.007490867,
+ 0.018593878, 0.0038187427, 0.029590257, -0.01451522, 0.016061379, 0.00008528442, -0.008943722, 0.01627464,
+ 0.024311995, -0.025911469, 0.00022596726, -0.008863748, 0.008823762, -0.034921836, 0.007910728, -0.01515501,
+ 0.035801545, -0.0035688248, -0.020299982, -0.03145631, -0.032256044, -0.028763862, -0.0071576433, -0.012769129,
+ 0.012322609, -0.006621153, 0.010583182, 0.024085402, -0.001623632, 0.007864078, -0.021406285, 0.002554159,
+ 0.012229307, -0.011762793, 0.0051682983, 0.0048484034, 0.018087378, 0.024325324, -0.037694257, -0.026537929,
+ -0.008803768, -0.017767483, -0.012642504, -0.0062712682, 0.0009771782, -0.010409906, 0.017754154, -0.004671795,
+ -0.030469967, 0.008477209, -0.005218282, -0.0058480743, -0.020153364, -0.0032805866, 0.004248601, 0.0051449724,
+ 0.006791097, 0.007650814, 0.003458861, -0.0031223053, -0.01932697, -0.033615597, 0.00745088, 0.006321252,
+ -0.0038154104, 0.014555207, 0.027697546, -0.02828402, 0.0066711367, 0.0077107945, 0.01794076, 0.011349596,
+ -0.0052715978, 0.014755142, -0.019753495, -0.011156326, 0.011202978, 0.022126047, 0.00846388, 0.030549942,
+ -0.0041386373, 0.018847128, -0.00033655585, 0.024925126, -0.003555496, -0.019300312, 0.010749794, 0.0075308536,
+ -0.018287312, -0.016567878, -0.012869096, -0.015528221, 0.0078107617, -0.011156326, 0.013522214, -0.020646535,
+ -0.01211601, 0.055928253, 0.011596181, -0.017247654, 0.0005939711, -0.026977783, -0.003942035, -0.009583511,
+ -0.0055248477, -0.028737204, 0.023179034, 0.003995351, 0.0219661, -0.008470545, 0.023392297, 0.010469886,
+ -0.015874773, 0.007890735, -0.009690142, -0.00024970944, 0.012775794, 0.0114762215, 0.013422247, 0.010429899,
+ -0.03686786, -0.006717788, -0.027484283, 0.011556195, -0.036068123, -0.013915418, -0.0016327957, 0.0151016945,
+ -0.020473259, 0.004671795, -0.012555866, 0.0209531, 0.01982014, 0.024485271, 0.0105431955, -0.005178295,
+ 0.033162415, -0.013795458, 0.007150979, 0.010243294, 0.005644808, 0.017260984, -0.0045618312, 0.0024725192,
+ 0.004305249, -0.008197301, 0.0014203656, 0.0018460588, 0.005015015, -0.011142998, 0.01439526, 0.022965772,
+ 0.02552493, 0.007757446, -0.0019726837, 0.009503538, -0.032042783, 0.008403899, -0.04609149, 0.013808787,
+ 0.011749465, 0.036388017, 0.016314628, 0.021939443, -0.0250051, -0.017354285, -0.012962398, 0.00006107364,
+ 0.019113706, 0.03081652, -0.018114036, -0.0084572155, 0.009643491, -0.0034721901, 0.0072642746, -0.0090636825,
+ 0.01642126, 0.013428912, 0.027724205, 0.0071243206, -0.6858542, -0.031029783, -0.014595194, -0.011449563,
+ 0.017514233, 0.01743426, 0.009950057, 0.0029706885, -0.015714826, -0.001806072, 0.011856096, 0.026444625,
+ -0.0010663156, -0.006474535, 0.0016161345, -0.020313311, 0.0148351155, -0.0018393943, 0.0057347785, 0.018300641,
+ -0.018647194, 0.03345565, -0.008070676, 0.0071443142, 0.014301958, 0.0044818576, 0.003838736, -0.007350913,
+ -0.024525259, -0.001142124, -0.018620536, 0.017247654, 0.007037683, 0.010236629, 0.06046009, 0.0138887605,
+ -0.012122675, 0.037694257, 0.0055081863, 0.042492677, 0.00021784494, -0.011656162, 0.010276617, 0.022325981,
+ 0.005984696, -0.009496873, 0.013382261, -0.0010563189, 0.0026507939, -0.041639622, 0.008637156, 0.026471283,
+ -0.008403899, 0.024858482, -0.00066686375, -0.0016252982, 0.027590916, 0.0051449724, 0.0058647357, -0.008743787,
+ -0.014968405, 0.027724205, -0.011596181, 0.0047650975, -0.015381602, 0.0043718936, 0.002159289, 0.035908177,
+ -0.008243952, -0.030443309, 0.027564257, 0.042625964, -0.0033688906, 0.01843393, 0.019087048, 0.024578573,
+ 0.03268257, -0.015608194, -0.014128681, -0.0033538956, -0.0028757197, -0.004121976, -0.032389335, 0.0034322033,
+ 0.058807302, 0.010943064, -0.030523283, 0.008903735, 0.017500903, 0.00871713, -0.0029406983, 0.013995391,
+ -0.03132302, -0.019660193, -0.00770413, -0.0038853872, 0.0015894766, -0.0015294964, -0.006251275, -0.021099718,
+ -0.010256623, -0.008863748, 0.028550599, 0.02020668, -0.0012962399, -0.003415542, -0.0022509254, 0.0119360695,
+ 0.027590916, -0.046971202, -0.0015194997, -0.022405956, 0.0016677842, -0.00018535563, -0.015421589, -0.031802863,
+ 0.03814744, 0.0065411795, 0.016567878, -0.015621523, 0.022899127, -0.011076353, 0.02841731, -0.002679118,
+ -0.002342562, 0.015341615, 0.01804739, -0.020566562, -0.012989056, -0.002990682, 0.01643459, 0.00042527664,
+ 0.008243952, -0.013715484, -0.004835075, -0.009803439, 0.03129636, -0.021432944, 0.0012087687, -0.015741484,
+ -0.0052016205, 0.00080890034, -0.01755422, 0.004811749, -0.017967418, -0.026684547, -0.014128681, 0.0041386373,
+ -0.013742141, -0.010056688, -0.013268964, -0.0110630235, -0.028337335, 0.015981404, -0.00997005, -0.02424535,
+ -0.013968734, -0.028310679, -0.027750863, -0.020699851, 0.02235264, 0.001057985, 0.00081639783, -0.0099367285,
+ 0.013522214, -0.012016043, -0.00086471526, 0.013568865, 0.0019376953, -0.019020405, 0.017460918, -0.023045745,
+ 0.008503866, 0.0064678704, -0.011509543, 0.018727167, -0.003372223, -0.0028690554, -0.0027024434, -0.011902748,
+ -0.012182655, -0.015714826, -0.0098634185, 0.00593138, 0.018753825, 0.0010146659, 0.013029044, 0.0003521757,
+ -0.017620865, 0.04102649, 0.00552818, 0.024485271, -0.009630162, -0.015608194, 0.0006718621, -0.0008418062,
+ 0.012395918, 0.0057980907, 0.016221326, 0.010616505, 0.004838407, -0.012402583, 0.019900113, -0.0034521967,
+ 0.000247002, -0.03153628, 0.0011038032, -0.020819811, 0.016234655, -0.00330058, -0.0032289368, 0.00078973995,
+ -0.021952773, -0.022459272, 0.03118973, 0.03673457, -0.021472929, 0.0072109587, -0.015075036, 0.004855068,
+ -0.0008151483, 0.0069643734, 0.010023367, -0.010276617, -0.023019087, 0.0068244194, -0.0012520878, -0.0015086699,
+ 0.022046074, -0.034148756, -0.0022192693, 0.002427534, -0.0027124402, 0.0060346797, 0.015461575, 0.0137554705,
+ 0.009230294, -0.009583511, 0.032629255, 0.015994733, -0.019167023, -0.009203636, 0.03393549, -0.017274313,
+ -0.012042701, -0.0009930064, 0.026777849, -0.013582194, -0.0027590916, -0.017594207, -0.026804507, -0.0014236979,
+ -0.022032745, 0.0091236625, -0.0042419364, -0.00858384, -0.0033905501, -0.020739838, 0.016821127, 0.022539245,
+ 0.015381602, 0.015141681, 0.028817179, -0.019726837, -0.0051283115, -0.011489551, -0.013208984, -0.0047017853,
+ -0.0072309524, 0.01767418, 0.0025658219, -0.010323267, 0.012609182, -0.028097415, 0.026871152, -0.010276617,
+ 0.021912785, 0.0022542577, 0.005124979, -0.0019710176, 0.004518512, -0.040360045, 0.010969722, -0.0031539614,
+ -0.020366628, -0.025778178, -0.0110030435, -0.016221326, 0.0036587953, 0.016207997, 0.003007343, -0.0032555948,
+ 0.0044052163, -0.022046074, -0.0008822095, -0.009363583, 0.028230704, -0.024538586, 0.0029840174, 0.0016044717,
+ -0.014181997, 0.031349678, -0.014381931, -0.027750863, 0.02613806, 0.0004136138, -0.005748107, -0.01868718,
+ -0.0010138329, 0.0054348772, 0.010703143, -0.003682121, 0.0030856507, -0.004275259, -0.010403241, 0.021113047,
+ -0.022685863, -0.023032416, 0.031429652, 0.001792743, -0.005644808, -0.011842767, -0.04078657, -0.0026874484,
+ 0.06915057, -0.00056939584, -0.013995391, 0.010703143, -0.013728813, -0.022939114, -0.015261642, -0.022485929,
+ 0.016807798, 0.007964044, 0.0144219175, 0.016821127, 0.0076241563, 0.005461535, -0.013248971, 0.015301628,
+ 0.0085171955, -0.004318578, 0.011136333, -0.0059047225, -0.010249958, -0.018207338, 0.024645219, 0.021752838,
+ 0.0007614159, -0.013648839, 0.01111634, -0.010503208, -0.0038487327, -0.008203966, -0.00397869, 0.0029740208,
+ 0.008530525, 0.005261601, 0.01642126, -0.0038753906, -0.013222313, 0.026537929, 0.024671877, -0.043505676,
+ 0.014195326, 0.024778508, 0.0056914594, -0.025951454, 0.017620865, -0.0021359634, 0.008643821, 0.021299653,
+ 0.0041686273, -0.009017031, 0.04044002, 0.024378639, -0.027777521, -0.014208655, 0.0028623908, 0.042119466,
+ 0.005801423, -0.028124074, -0.03129636, 0.022139376, -0.022179363, -0.04067994, 0.013688826, 0.013328944,
+ 0.0046184794, -0.02828402, -0.0063412455, -0.0046184794, -0.011756129, -0.010383247, -0.0018543894, -0.0018593877,
+ -0.00052024535, 0.004815081, 0.014781799, 0.018007403, 0.01306903, -0.020433271, 0.009043689, 0.033189073,
+ -0.006844413, -0.019766824, -0.018767154, 0.00533491, -0.0024575242, 0.018727167, 0.0058080875, -0.013835444,
+ 0.0040719924, 0.004881726, 0.012029372, 0.005664801, 0.03193615, 0.0058047553, 0.002695779, 0.009290274,
+ 0.02361889, 0.017834127, 0.0049017193, -0.0036388019, 0.010776452, -0.019793482, 0.0067777685, -0.014208655,
+ -0.024911797, 0.002385881, 0.0034988478, 0.020899786, -0.0025858153, -0.011849431, 0.033189073, -0.021312982,
+ 0.024965113, -0.014635181, 0.014048708, -0.0035921505, -0.003347231, 0.030869836, -0.0017161017, -0.0061346465,
+ 0.009203636, -0.025165047, 0.0068510775, 0.021499587, 0.013782129, -0.0024475274, -0.0051149824, -0.024445284,
+ 0.006167969, 0.0068844, -0.00076183246, 0.030150073, -0.0055948244, -0.011162991, -0.02057989, -0.009703471,
+ -0.020646535, 0.008004031, 0.0066378145, -0.019900113, -0.012169327, -0.01439526, 0.0044252095, -0.004018677,
+ 0.014621852, -0.025085073, -0.013715484, -0.017980747, 0.0071043274, 0.011456228, -0.01010334, -0.0035321703,
+ -0.03801415, -0.012036037, -0.0028990454, -0.05419549, -0.024058744, -0.024272008, 0.015221654, 0.027964126,
+ 0.03182952, -0.015354944, 0.004855068, 0.011522872, 0.004771762, 0.0027874154, 0.023405626, 0.0004242353,
+ -0.03132302, 0.007057676, 0.008763781, -0.0027057757, 0.023005757, -0.0071176565, -0.005238275, 0.029110415,
+ -0.010989714, 0.013728813, -0.009630162, -0.029137073, -0.0049317093, -0.0008630492, -0.015248313, 0.0043219104,
+ -0.0055681667, -0.013175662, 0.029723546, 0.025098402, 0.012849103, -0.0009996708, 0.03118973, -0.0021709518,
+ 0.0260181, -0.020526575, 0.028097415, -0.016141351, 0.010509873, -0.022965772, 0.002865723, 0.0020493253,
+ 0.0020509914, -0.0041419696, -0.00039695262, 0.017287642, 0.0038987163, 0.014795128, -0.014661839, -0.008950386,
+ 0.004431874, -0.009383577, 0.0012604183, -0.023019087, 0.0029273694, -0.033135757, 0.009176978, -0.011023037,
+ -0.002102641, 0.02663123, -0.03849399, -0.0044152127, 0.0004527676, -0.0026924468, 0.02828402, 0.017727496,
+ 0.035135098, 0.02728435, -0.005348239, -0.001467017, -0.019766824, 0.014715155, 0.011982721, 0.0045651635,
+ 0.023458943, -0.0010046692, -0.0031373003, -0.0006972704, 0.0019043729, -0.018967088, -0.024311995, 0.0011546199,
+ 0.007977373, -0.004755101, -0.010016702, -0.02780418, -0.004688456, 0.013022379, -0.005484861, 0.0017227661,
+ -0.015394931, -0.028763862, -0.026684547, 0.0030589928, -0.018513903, 0.028363993, 0.0044818576, -0.009270281,
+ 0.038920518, -0.016008062, 0.0093902415, 0.004815081, -0.021059733, 0.01451522, -0.0051583014, 0.023765508,
+ -0.017874114, -0.016821127, -0.012522544, -0.0028390652, 0.0040886537, 0.020259995, -0.031216389, -0.014115352,
+ -0.009176978, 0.010303274, 0.020313311, 0.0064112223, -0.02235264, -0.022872468, 0.0052449396, 0.0005723116,
+ 0.0037321046, 0.016807798, -0.018527232, -0.009303603, 0.0024858483, -0.0012662497, -0.007110992, 0.011976057,
+ -0.007790768, -0.042999174, -0.006727785, -0.011829439, 0.007024354, 0.005278262, -0.017740825, -0.0041519664,
+ 0.0085905045, 0.027750863, -0.038387362, 0.024391968, 0.00087721116, 0.010509873, -0.00038508154, -0.006857742,
+ 0.0183273, -0.0037054466, 0.015461575, 0.0017394272, -0.0017944091, 0.014181997, -0.0052682655, 0.009023695,
+ 0.00719763, -0.013522214, 0.0034422, 0.014941746, -0.0016711164, -0.025298337, -0.017634194, 0.0058714002,
+ -0.005321581, 0.017834127, 0.0110630235, -0.03369557, 0.029190388, -0.008943722, 0.009363583, -0.0034222065,
+ -0.026111402, -0.007037683, -0.006561173, 0.02473852, -0.007084334, -0.010110005, -0.008577175, 0.0030439978,
+ -0.022712521, 0.0054582027, -0.0012620845, -0.0011954397, -0.015741484, 0.0129557345, -0.00042111133, 0.00846388,
+ 0.008930393, 0.016487904, 0.010469886, -0.007917393, -0.011762793, -0.0214596, 0.000917198, 0.021672864,
+ 0.010269952, -0.007737452, -0.010243294, -0.0067244526, -0.015488233, -0.021552904, 0.017127695, 0.011109675,
+ 0.038067464, 0.00871713, -0.0025591573, 0.021312982, -0.006237946, 0.034628596, -0.0045251767, 0.008357248,
+ 0.020686522, 0.0010696478, 0.0076708077, 0.03772091, -0.018700508, -0.0020676525, -0.008923728, -0.023298996,
+ 0.018233996, -0.010256623, 0.0017860786, 0.009796774, -0.00897038, -0.01269582, -0.018527232, 0.009190307,
+ -0.02372552, -0.042119466, 0.008097334, -0.0066778013, -0.021046404, 0.0019593548, 0.011083017, -0.0016028056,
+ 0.012662497, -0.000059095124, 0.0071043274, -0.014675168, 0.024831824, -0.053582355, 0.038387362, 0.0005698124,
+ 0.015954746, 0.021552904, 0.031589597, -0.009230294, -0.0006147976, 0.002625802, -0.011749465, -0.034362018,
+ -0.0067844326, -0.018793812, 0.011442899, -0.008743787, 0.017474247, -0.021619547, 0.01831397, -0.009037024,
+ -0.0057247817, -0.02728435, 0.010363255, 0.034415334, -0.024032086, -0.0020126705, -0.0045518344, -0.019353628,
+ -0.018340627, -0.03129636, -0.0034038792, -0.006321252, -0.0016161345, 0.033642255, -0.000056075285, -0.005005019,
+ 0.004571828, -0.0024075406, -0.00010215386, 0.0098634185, 0.1980148, -0.003825407, -0.025191706, 0.035161756,
+ 0.005358236, 0.025111731, 0.023485601, 0.0023342315, -0.011882754, 0.018287312, -0.0068910643, 0.003912045,
+ 0.009243623, -0.001355387, -0.028603915, -0.012802451, -0.030150073, -0.014795128, -0.028630573, -0.0013487226,
+ 0.002667455, 0.00985009, -0.0033972147, -0.021486258, 0.009503538, -0.017847456, 0.013062365, -0.014341944,
+ 0.005078328, 0.025165047, -0.015594865, -0.025924796, -0.0018177348, 0.010996379, -0.02993681, 0.007324255,
+ 0.014475234, -0.028577257, 0.005494857, 0.00011725306, -0.013315615, 0.015941417, 0.009376912, 0.0025158382,
+ 0.008743787, 0.023832154, -0.008084005, -0.014195326, -0.008823762, 0.0033455652, -0.032362677, -0.021552904,
+ -0.0056081535, 0.023298996, -0.025444955, 0.0097301295, 0.009736794, 0.015274971, -0.0012937407, -0.018087378,
+ -0.0039387033, 0.008637156, -0.011189649, -0.00023846315, -0.011582852, 0.0066411467, -0.018220667, 0.0060846633,
+ 0.0376676, -0.002709108, 0.0072776037, 0.0034188742, -0.010249958, -0.0007747449, -0.00795738, -0.022192692,
+ 0.03910712, 0.032122757, 0.023898797, 0.0076241563, -0.007397564, -0.003655463, 0.011442899, -0.014115352,
+ -0.00505167, -0.031163072, 0.030336678, -0.006857742, -0.022259338, 0.004048667, 0.02072651, 0.0030156737,
+ -0.0042119464, 0.00041861215, -0.005731446, 0.011103011, 0.013822115, 0.021512916, 0.009216965, -0.006537847,
+ -0.027057758, -0.04054665, 0.010403241, -0.0056281467, -0.005701456, -0.002709108, -0.00745088, -0.0024841821,
+ 0.009356919, -0.022659205, 0.004061996, -0.013175662, 0.017074378, -0.006141311, -0.014541878, 0.02993681,
+ -0.00028448965, -0.025271678, 0.011689484, -0.014528549, 0.004398552, -0.017274313, 0.0045751603, 0.012455898,
+ 0.004121976, -0.025458284, -0.006744446, 0.011822774, -0.015035049, -0.03257594, 0.014675168, -0.0039187097,
+ 0.019726837, -0.0047251107, 0.0022825818, 0.011829439, 0.005391558, -0.016781142, -0.0058747325, 0.010309938,
+ -0.013049036, 0.01186276, -0.0011246296, 0.0062112883, 0.0028190718, -0.021739509, 0.009883412, -0.0073175905,
+ -0.012715813, -0.017181009, -0.016607866, -0.042492677, -0.0014478565, -0.01794076, 0.012302616, -0.015194997,
+ -0.04433207, -0.020606548, 0.009696807, 0.010303274, -0.01694109, -0.004018677, 0.019353628, -0.001991011,
+ 0.000058938927, 0.010536531, -0.17274313, 0.010143327, 0.014235313, -0.024152048, 0.025684876, -0.0012504216,
+ 0.036601283, -0.003698782, 0.0007310093, 0.004165295, -0.0029157067, 0.017101036, -0.046891227, -0.017460918,
+ 0.022965772, 0.020233337, -0.024072073, 0.017220996, 0.009370248, 0.0010363255, 0.0194336, -0.019606877,
+ 0.01818068, -0.020819811, 0.007410893, 0.0019326969, 0.017887443, 0.006651143, 0.00067394477, -0.011889419,
+ -0.025058415, -0.008543854, 0.021579562, 0.0047484366, 0.014062037, 0.0075508473, -0.009510202, -0.009143656,
+ 0.0046817916, 0.013982063, -0.0027990784, 0.011782787, 0.014541878, -0.015701497, -0.029350337, 0.021979429,
+ 0.01332228, -0.026244693, -0.0123492675, -0.003895384, 0.0071576433, -0.035454992, -0.00046984528, 0.0033522295,
+ 0.039347045, 0.0005119148, 0.00476843, -0.012995721, 0.0024042083, -0.006931051, -0.014461905, -0.0127558,
+ 0.0034555288, -0.0074842023, -0.030256703, -0.007057676, -0.00807734, 0.007804097, -0.006957709, 0.017181009,
+ -0.034575284, -0.008603834, -0.005008351, -0.015834786, 0.02943031, 0.016861115, -0.0050849924, 0.014235313,
+ 0.0051449724, 0.0025924798, -0.0025741523, 0.04289254, -0.002104307, 0.012969063, -0.008310596, 0.00423194,
+ 0.0074975314, 0.0018810473, -0.014248641, -0.024725191, 0.0151016945, -0.017527562, 0.0018727167, 0.0002830318,
+ 0.015168339, 0.0144219175, -0.004048667, -0.004358565, 0.011836103, -0.010343261, -0.005911387, 0.0022825818,
+ 0.0073175905, 0.00403867, 0.013188991, 0.03334902, 0.006111321, 0.008597169, 0.030123414, -0.015474904,
+ 0.0017877447, -0.024551915, 0.013155668, 0.023525586, -0.0255116, 0.017220996, 0.004358565, -0.00934359,
+ 0.0099967085, 0.011162991, 0.03092315, -0.021046404, -0.015514892, 0.0011946067, -0.01816735, 0.010876419,
+ -0.10124666, -0.03550831, 0.0056348112, 0.013942076, 0.005951374, 0.020419942, -0.006857742, -0.020873128,
+ -0.021259667, 0.0137554705, 0.0057880944, -0.029163731, -0.018767154, -0.021392956, 0.030896494, -0.005494857,
+ -0.0027307675, -0.006801094, -0.014821786, 0.021392956, -0.0018110704, -0.0018843795, -0.012362596, -0.0072176233,
+ -0.017194338, -0.018713837, -0.024272008, 0.03801415, 0.00015880188, 0.0044951867, -0.028630573, -0.0014070367,
+ -0.00916365, -0.026537929, -0.009576847, -0.013995391, -0.0077107945, 0.0050016865, 0.00578143, -0.04467862,
+ 0.008363913, 0.010136662, -0.0006268769, -0.006591163, 0.015341615, -0.027377652, -0.00093136, 0.029243704,
+ -0.020886457, -0.01041657, -0.02424535, 0.005291591, -0.02980352, -0.009190307, 0.019460259, -0.0041286405,
+ 0.004801752, 0.0011787785, -0.001257086, -0.011216307, -0.013395589, 0.00088137644, -0.0051616337, 0.03876057,
+ -0.0033455652, 0.00075850025, -0.006951045, -0.0062112883, 0.018140694, -0.006351242, -0.008263946, 0.018154023,
+ -0.012189319, 0.0075508473, -0.044358727, -0.0040153447, 0.0093302615, -0.010636497, 0.032789204, -0.005264933,
+ -0.014235313, -0.018393943, 0.007297597, -0.016114693, 0.015021721, 0.020033404, 0.0137688, 0.0011046362,
+ 0.010616505, -0.0039453674, 0.012109346, 0.021099718, -0.0072842683, -0.019153694, -0.003768759, 0.039320387,
+ -0.006747778, -0.0016852784, 0.018154023, 0.0010963057, -0.015035049, -0.021033075, -0.04345236, 0.017287642,
+ 0.016341286, -0.008610498, 0.00236922, 0.009290274, 0.028950468, -0.014475234, -0.0035654926, 0.015434918,
+ -0.03372223, 0.004501851, -0.012929076, -0.008483873, -0.0044685286, -0.0102233, 0.01615468, 0.0022792495,
+ 0.010876419, -0.0059647025, 0.01895376, -0.0069976957, -0.0042952523, 0.017207667, -0.00036133936, 0.0085905045,
+ 0.008084005, 0.03129636, -0.016994404, -0.014915089, 0.020100048, -0.012009379, -0.006684466, 0.01306903,
+ 0.00015765642, -0.00530492, 0.0005277429, 0.015421589, 0.015528221, 0.032202728, -0.003485519, -0.0014286962,
+ 0.033908837, 0.001367883, 0.010509873, 0.025271678, -0.020993087, 0.019846799, 0.006897729, -0.010216636,
+ -0.00725761, 0.01818068, -0.028443968, -0.011242964, -0.014435247, -0.013688826, 0.006101324, -0.0022509254,
+ 0.013848773, -0.0019077052, 0.017181009, 0.03422873, 0.005324913, -0.0035188415, 0.014128681, -0.004898387,
+ 0.005038341, 0.0012320944, -0.005561502, -0.017847456, 0.0008538855, -0.0047884234, 0.011849431, 0.015421589,
+ -0.013942076, 0.0029790192, -0.013702155, 0.0001199605, -0.024431955, 0.019926772, 0.022179363, -0.016487904,
+ -0.03964028, 0.0050849924, 0.017487574, 0.022792496, 0.0012504216, 0.004048667, -0.00997005, 0.0076041627,
+ -0.014328616, -0.020259995, 0.0005598157, -0.010469886, 0.0016852784, 0.01716768, -0.008990373, -0.001987679,
+ 0.026417969, 0.023792166, 0.0046917885, -0.0071909656, -0.00032051947, -0.023259008, -0.009170313, 0.02071318,
+ -0.03156294, -0.030869836, -0.006324584, 0.013795458, -0.00047151142, 0.016874444, 0.00947688, 0.00985009,
+ -0.029883493, 0.024205362, -0.013522214, -0.015075036, -0.030603256, 0.029270362, 0.010503208, 0.021539574,
+ 0.01743426, -0.023898797, 0.022019416, -0.0068777353, 0.027857494, -0.021259667, 0.0025758184, 0.006197959,
+ 0.006447877, -0.00025200035, -0.004941706, -0.021246338, -0.005504854, -0.008390571, -0.0097301295, 0.027244363,
+ -0.04446536, 0.05216949, 0.010243294, -0.016008062, 0.0122493, -0.0199401, 0.009077012, 0.019753495, 0.006431216,
+ -0.037960835, -0.027377652, 0.016381273, -0.0038620618, 0.022512587, -0.010996379, -0.0015211658, -0.0102233,
+ 0.007071005, 0.008230623, -0.009490209, -0.010083347, 0.024431955, 0.002427534, 0.02828402, 0.0035721571,
+ -0.022192692, -0.011882754, 0.010056688, 0.0011904413, -0.01426197, -0.017500903, -0.00010985966, 0.005591492,
+ -0.0077707744, -0.012049366, 0.011869425, 0.00858384, -0.024698535, -0.030283362, 0.020140035, 0.011949399,
+ -0.013968734, 0.042732596, -0.011649498, -0.011982721, -0.016967745, -0.0060913274, -0.007130985, -0.013109017,
+ -0.009710136 };
+
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
index f44e094705..1f9a006f61 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
@@ -33,6 +33,7 @@
import java.util.stream.Stream;
import org.assertj.core.data.Percentage;
+import org.bson.BsonDouble;
import org.bson.BsonUndefined;
import org.bson.types.Binary;
import org.bson.types.Code;
@@ -70,6 +71,7 @@
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.convert.WritingConverter;
+import org.springframework.data.domain.Vector;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
@@ -3328,6 +3330,24 @@ void shouldReadNonIdFieldCalledIdFromSource() {
assertThat(target.id).isEqualTo(source.id);
}
+ @Test // GH-4706
+ void shouldWriteVectorValues() {
+
+ WithVector source = new WithVector();
+ source.embeddings = Vector.of(1.1d, 2.2d, 3.3d);
+
+ org.bson.Document document = write(source);
+ assertThat(document.getList("embeddings", BsonDouble.class)).hasSize(3);
+ }
+
+ @Test // GH-4706
+ void shouldReadVectorValues() {
+
+ org.bson.Document document = new org.bson.Document("embeddings", List.of(1.1d, 2.2d, 3.3d));
+ WithVector withVector = converter.read(WithVector.class, document);
+ assertThat(withVector.embeddings.toDoubleArray()).contains(1.1d, 2.2d, 3.3d);
+ }
+
org.bson.Document write(Object source) {
org.bson.Document target = new org.bson.Document();
@@ -3335,6 +3355,11 @@ org.bson.Document write(Object source) {
return target;
}
+ static class WithVector {
+
+ Vector embeddings;
+ }
+
static class GenericType {
T content;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersIntegrationTests.java
index dd7d454f3d..b57ab35ea1 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersIntegrationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersIntegrationTests.java
@@ -23,17 +23,22 @@
import java.util.Objects;
import java.util.UUID;
+import org.bson.BinaryVector;
import org.bson.types.Binary;
+import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.annotation.Id;
+import org.springframework.data.domain.Vector;
import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.MongoVector;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
import org.springframework.data.mongodb.test.util.Template;
+import org.springframework.util.ObjectUtils;
/**
* Integration tests for {@link MongoConverters}.
@@ -101,6 +106,78 @@ public void shouldReadBinaryType() {
assertThat(template.findOne(query(where("id").is(wbd.id)), WithBinaryDataType.class)).isEqualTo(wbd);
}
+ @Test // GH-4706
+ public void shouldReadAndWriteVectors() {
+
+ WithVectors source = new WithVectors();
+ source.vector = Vector.of(1.1, 2.2, 3.3);
+
+ template.save(source);
+
+ WithVectors loaded = template.findOne(query(where("id").is(source.id)), WithVectors.class);
+ assertThat(loaded).isEqualTo(source);
+ }
+
+ @Test // GH-4706
+ public void shouldReadAndWriteFloatVectors() {
+
+ WithVectors source = new WithVectors();
+ source.vector = Vector.of(1.1f, 2.2f, 3.3f);
+
+ template.save(source);
+
+ WithVectors loaded = template.findOne(query(where("id").is(source.id)), WithVectors.class);
+
+ // top-level arrays are converted into doubles by MongoDB with all their conversion imprecisions
+ assertThat(loaded.vector.getClass().getName()).contains("DoubleVector");
+ assertThat(loaded.vector).isNotEqualTo(source.vector);
+ }
+
+ @Test // GH-4706
+ public void shouldReadAndWriteBinFloat32Vectors() {
+
+ WithVectors source = new WithVectors();
+ source.binVector = BinaryVector.floatVector(new float[] { 1.1f, 2.2f, 3.3f });
+ source.vector = MongoVector.of(source.binVector);
+
+ template.save(source);
+
+ WithVectors loaded = template.findOne(query(where("id").is(source.id)), WithVectors.class);
+
+ assertThat(loaded.vector).isEqualTo(source.vector);
+ assertThat(loaded.binVector).isEqualTo(source.binVector);
+ }
+
+ @Test // GH-4706
+ public void shouldReadAndWriteBinInt8Vectors() {
+
+ WithVectors source = new WithVectors();
+ source.binVector = BinaryVector.int8Vector(new byte[] { 1, 2, 3 });
+ source.vector = MongoVector.of(source.binVector);
+
+ template.save(source);
+
+ WithVectors loaded = template.findOne(query(where("id").is(source.id)), WithVectors.class);
+
+ assertThat(loaded.vector).isEqualTo(source.vector);
+ assertThat(loaded.binVector).isEqualTo(source.binVector);
+ }
+
+ @Test // GH-4706
+ public void shouldReadAndWriteBinPackedVectors() {
+
+ WithVectors source = new WithVectors();
+ source.binVector = BinaryVector.packedBitVector(new byte[] { 1, 2, 3 }, (byte) 1);
+ source.vector = MongoVector.of(source.binVector);
+
+ template.save(source);
+
+ WithVectors loaded = template.findOne(query(where("id").is(source.id)), WithVectors.class);
+
+ assertThat(loaded.vector).isEqualTo(source.vector);
+ assertThat(loaded.binVector).isEqualTo(source.binVector);
+ }
+
@Document(COLLECTION)
static class Wrapper {
@@ -108,6 +185,33 @@ static class Wrapper {
UUID uuid;
}
+ @Document(COLLECTION)
+ static class WithVectors {
+
+ ObjectId id;
+ Vector vector;
+ BinaryVector binVector;
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof WithVectors that)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(id, that.id)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(vector, that.vector)) {
+ return false;
+ }
+ return ObjectUtils.nullSafeEquals(binVector, that.binVector);
+ }
+
+ @Override
+ public int hashCode() {
+ return ObjectUtils.nullSafeHash(id, vector, binVector);
+ }
+ }
+
@Document(COLLECTION)
static class WithBinaryDataInArray {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java
index 1e7e1ffe84..aa26445f2d 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java
@@ -15,9 +15,8 @@
*/
package org.springframework.data.mongodb.core.index;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
-import static org.springframework.data.mongodb.test.util.Assertions.assertThatExceptionOfType;
+import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -32,6 +31,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
+
import org.springframework.core.annotation.AliasFor;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
@@ -328,7 +328,8 @@ class IndexOnLevelOneWithExplicitlyNamedField {
class IndexOnLevelZeroWithExplicityNamedField {
- @Indexed @Field("customFieldName") String namedProperty;
+ @Indexed
+ @Field("customFieldName") String namedProperty;
}
@Document
@@ -441,7 +442,8 @@ class WithPartialFilter {
@Document
class IndexOnMetaAnnotatedField {
- @Field("_name") @IndexedFieldAnnotation String lastname;
+ @Field("_name")
+ @IndexedFieldAnnotation String lastname;
}
/**
@@ -839,7 +841,8 @@ class CompoundIndexWithCollation {}
class WithCompoundCollationFromDocument {}
@Document(collation = "{'locale': 'en_US', 'strength': 2}")
- @CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}", collation = "#{{ 'locale' : 'de' + '_' + 'AT' }}")
+ @CompoundIndex(name = "compound_index_with_collation", def = "{'foo': 1}",
+ collation = "#{{ 'locale' : 'de' + '_' + 'AT' }}")
class WithEvaluatedCollationFromCompoundIndex {}
}
@@ -1474,9 +1477,9 @@ public void indexedWithCollation() {
WithCollationFromIndexedAnnotation.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
- .append("unique", true)
- .append("collation", new org.bson.Document().append("locale", "en_US").append("strength", 2)));
+ assertThat(indexDefinition.getIndexOptions())
+ .isEqualTo(new org.bson.Document().append("name", "value").append("unique", true).append("collation",
+ new org.bson.Document().append("locale", "en_US").append("strength", 2)));
}
@Test // GH-3002
@@ -1486,9 +1489,9 @@ public void indexedWithCollationFromDocumentAnnotation() {
WithCollationFromDocumentAnnotation.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "value")
- .append("unique", true)
- .append("collation", new org.bson.Document().append("locale", "en_US").append("strength", 2)));
+ assertThat(indexDefinition.getIndexOptions())
+ .isEqualTo(new org.bson.Document().append("name", "value").append("unique", true).append("collation",
+ new org.bson.Document().append("locale", "en_US").append("strength", 2)));
}
@Test // GH-3002
@@ -1591,7 +1594,8 @@ class ValueObject {
@Document
class SimilarityHolingBean {
- @Indexed @Field("norm") String normalProperty;
+ @Indexed
+ @Field("norm") String normalProperty;
@Field("similarityL") private List listOfSimilarilyNamedEntities = null;
}
@@ -1754,7 +1758,8 @@ class EntityWithGenericTypeWrapperAsElement {
@Document
class WithHashedIndexOnId {
- @HashIndexed @Id String id;
+ @HashIndexed
+ @Id String id;
}
@Document
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/VectorIndexIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/VectorIndexIntegrationTests.java
new file mode 100644
index 0000000000..470922e7e6
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/VectorIndexIntegrationTests.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2025 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.mongodb.core.index;
+
+import static org.awaitility.Awaitility.*;
+import static org.springframework.data.mongodb.test.util.Assertions.*;
+
+import java.util.List;
+
+import org.bson.Document;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.index.VectorIndex.SimilarityFunction;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.test.util.EnableIfVectorSearchAvailable;
+import org.springframework.data.mongodb.test.util.MongoTestTemplate;
+import org.springframework.lang.Nullable;
+
+import com.mongodb.client.AggregateIterable;
+
+/**
+ * Integration tests for vector index creation.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ */
+@EnableIfVectorSearchAvailable
+class VectorIndexIntegrationTests {
+
+ MongoTestTemplate template = new MongoTestTemplate(cfg -> {
+ cfg.configureMappingContext(ctx -> {
+ ctx.initialEntitySet(Movie.class);
+ });
+ });
+
+ SearchIndexOperations indexOps;
+
+ @BeforeEach
+ void init() {
+ template.createCollection(Movie.class);
+ indexOps = template.searchIndexOps(Movie.class);
+ }
+
+ @AfterEach
+ void cleanup() {
+
+ template.searchIndexOps(Movie.class).dropAllIndexes();
+ template.dropCollection(Movie.class);
+ }
+
+ @ParameterizedTest // GH-4706
+ @ValueSource(strings = { "euclidean", "cosine", "dotProduct" })
+ void createsSimpleVectorIndex(String similarityFunction) throws InterruptedException {
+
+ VectorIndex idx = new VectorIndex("vector_index").addVector("plotEmbedding",
+ builder -> builder.dimensions(1536).similarity(similarityFunction));
+
+ indexOps.ensureIndex(idx);
+
+ await().untilAsserted(() -> {
+ Document raw = readRawIndexInfo(idx.getName());
+ assertThat(raw).containsEntry("name", idx.getName()) //
+ .containsEntry("type", "vectorSearch") //
+ .containsEntry("latestDefinition.fields.[0].type", "vector") //
+ .containsEntry("latestDefinition.fields.[0].path", "plot_embedding") //
+ .containsEntry("latestDefinition.fields.[0].numDimensions", 1536) //
+ .containsEntry("latestDefinition.fields.[0].similarity", similarityFunction); //
+ });
+ }
+
+ @Test // GH-4706
+ void updatesVectorIndex() {
+
+ String indexName = "vector_index";
+ VectorIndex idx = new VectorIndex(indexName).addVector("plotEmbedding",
+ builder -> builder.dimensions(1536).similarity("cosine"));
+
+ indexOps.ensureIndex(idx);
+
+ await().untilAsserted(() -> {
+ Document raw = readRawIndexInfo(idx.getName());
+ assertThat(raw).containsEntry("name", idx.getName()) //
+ .containsEntry("type", "vectorSearch") //
+ .containsEntry("latestDefinition.fields.[0].type", "vector") //
+ .containsEntry("latestDefinition.fields.[0].path", "plot_embedding") //
+ .containsEntry("latestDefinition.fields.[0].numDimensions", 1536) //
+ .containsEntry("latestDefinition.fields.[0].similarity", "cosine"); //
+ });
+
+ VectorIndex updatedIdx = new VectorIndex(indexName).addVector("plotEmbedding",
+ builder -> builder.dimensions(1536).similarity(SimilarityFunction.DOT_PRODUCT));
+ assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> indexOps.updateIndex(idx));
+ }
+
+ @Test // GH-4706
+ void createsVectorIndexWithFilters() {
+
+ VectorIndex idx = new VectorIndex("vector_index")
+ .addVector("plotEmbedding", builder -> builder.dimensions(1536).cosine()).addFilter("description")
+ .addFilter("year");
+
+ indexOps.ensureIndex(idx);
+
+ await().untilAsserted(() -> {
+ Document raw = readRawIndexInfo(idx.getName());
+ assertThat(raw).containsEntry("name", idx.getName()) //
+ .containsEntry("type", "vectorSearch") //
+ .containsEntry("latestDefinition.fields.[0].type", "vector") //
+ .containsEntry("latestDefinition.fields.[1].type", "filter") //
+ .containsEntry("latestDefinition.fields.[1].path", "plot") //
+ .containsEntry("latestDefinition.fields.[2].type", "filter") //
+ .containsEntry("latestDefinition.fields.[2].path", "year"); //
+ });
+ }
+
+ @Nullable
+ private Document readRawIndexInfo(String name) {
+
+ AggregateIterable indexes = template.execute(Movie.class, collection -> {
+ return collection.aggregate(List.of(new Document("$listSearchIndexes", new Document("name", name))));
+ });
+
+ return indexes.first();
+ }
+
+ static class Movie {
+
+ @Id String id;
+ String title;
+
+ @Field("plot") String description;
+ int year;
+
+ @Field("plot_embedding") Double[] plotEmbedding;
+ }
+
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/EnableIfVectorSearchAvailable.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/EnableIfVectorSearchAvailable.java
new file mode 100644
index 0000000000..da008d9ee4
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/EnableIfVectorSearchAvailable.java
@@ -0,0 +1,37 @@
+/*
+ * 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
+ *
+ * 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.mongodb.test.util;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * @author Christoph Strobl
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Tag("vector-search")
+@ExtendWith(MongoServerCondition.class)
+public @interface EnableIfVectorSearchAvailable {
+
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
index 0afd0ea643..d811e0a1ef 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoServerCondition.java
@@ -42,6 +42,12 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
}
}
+ if(context.getTags().contains("vector-search")) {
+ if(!atlasEnvironment(context)) {
+ return ConditionEvaluationResult.disabled("Disabled for servers not supporting Vector Search.");
+ }
+ }
+
if (context.getTags().contains("version-specific") && context.getElement().isPresent()) {
EnableIfMongoServerVersion version = AnnotatedElementUtils.findMergedAnnotation(context.getElement().get(),
@@ -83,4 +89,9 @@ private Version serverVersion(ExtensionContext context) {
return context.getStore(NAMESPACE).getOrComputeIfAbsent(Version.class, (key) -> MongoTestUtils.serverVersion(),
Version.class);
}
+
+ private boolean atlasEnvironment(ExtensionContext context) {
+ return context.getStore(NAMESPACE).getOrComputeIfAbsent(Version.class, (key) -> MongoTestUtils.isVectorSearchEnabled(),
+ Boolean.class);
+ }
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
index 26153f79f0..a9dc1b14be 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
@@ -262,6 +262,22 @@ public static boolean serverIsReplSet() {
}
}
+ @SuppressWarnings("unchecked")
+ public static boolean isVectorSearchEnabled() {
+ try (MongoClient client = MongoTestUtils.client()) {
+
+ return client.getDatabase("admin").runCommand(new Document("getCmdLineOpts", "1")).get("argv", List.class)
+ .stream().anyMatch(it -> {
+ if(it instanceof String cfgString) {
+ return cfgString.startsWith("searchIndexManagementHostAndPort");
+ }
+ return false;
+ });
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
public static Duration getTimeout() {
return ObjectUtils.nullSafeEquals("jenkins", ENV.getProperty("user.name")) ? Duration.ofMillis(100)