From b183f1b9d741eeb3abf42eec65bba961217d7a07 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 15 Sep 2023 16:40:00 -0600 Subject: [PATCH 1/7] Introduce builder for `$vectorSearch` aggregation stage JAVA-5117 --- .../com/mongodb/client/model/Aggregates.java | 79 ++++++++++ .../com/mongodb/client/model/Filters.java | 8 +- .../com/mongodb/client/model/Projections.java | 16 +++ .../search/VectorSearchConstructibleBson.java | 54 +++++++ .../model/search/VectorSearchOptions.java | 71 +++++++++ .../client/model/search/package-info.java | 1 + .../AggregatesSearchIntegrationTest.java | 135 ++++++++++++++++-- .../model/AggregatesSpecification.groovy | 64 ++++++++- .../client/model/FiltersSpecification.groovy | 58 ++++---- .../model/ProjectionsSpecification.groovy | 5 +- .../model/search/VectorSearchOptionsTest.java | 84 +++++++++++ .../org/mongodb/scala/model/Aggregates.scala | 52 ++++++- .../org/mongodb/scala/model/Filters.scala | 6 +- .../model/search/VectorSearchOptions.scala | 37 +++++ .../mongodb/scala/model/search/package.scala | 10 ++ .../mongodb/scala/model/AggregatesSpec.scala | 62 +++++++- .../org/mongodb/scala/model/FiltersSpec.scala | 34 ++--- 17 files changed, 698 insertions(+), 78 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/search/VectorSearchConstructibleBson.java create mode 100644 driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java create mode 100644 driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java create mode 100644 driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index 83c63e2b642..e6ea14a87a2 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -17,14 +17,17 @@ package com.mongodb.client.model; import com.mongodb.MongoNamespace; +import com.mongodb.annotations.Beta; import com.mongodb.client.model.densify.DensifyOptions; import com.mongodb.client.model.densify.DensifyRange; import com.mongodb.client.model.fill.FillOptions; import com.mongodb.client.model.fill.FillOutputField; import com.mongodb.client.model.geojson.Point; +import com.mongodb.client.model.search.FieldSearchPath; import com.mongodb.client.model.search.SearchCollector; import com.mongodb.client.model.search.SearchOperator; import com.mongodb.client.model.search.SearchOptions; +import com.mongodb.client.model.search.VectorSearchOptions; import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -34,6 +37,7 @@ import org.bson.BsonString; import org.bson.BsonType; import org.bson.BsonValue; +import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; @@ -47,6 +51,7 @@ import static com.mongodb.client.model.GeoNearOptions.geoNearOptions; import static com.mongodb.client.model.densify.DensifyOptions.densifyOptions; import static com.mongodb.client.model.search.SearchOptions.searchOptions; +import static com.mongodb.client.model.search.VectorSearchOptions.vectorSearchOptions; import static com.mongodb.internal.Iterables.concat; import static com.mongodb.internal.client.model.Util.sizeAtLeast; import static java.util.Arrays.asList; @@ -933,6 +938,80 @@ public static Bson searchMeta(final SearchCollector collector, final SearchOptio return new SearchStage("$searchMeta", notNull("collector", collector), notNull("options", options)); } + /** + * VAKOTODO + * @param path + * @param queryVector + * @param index + * @param numCandidates + * @param limit + * @return + * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * @mongodb.server.release 7.1 + * @since 4.11 + */ + @Beta(Beta.Reason.SERVER) + public static Bson vectorSearch( + final FieldSearchPath path, + final Iterable queryVector, + final String index, + final long numCandidates, + final long limit) { + return vectorSearch(notNull("path", path), notNull("queryVector", queryVector), notNull("index", index), numCandidates, limit, + vectorSearchOptions()); + } + + /** + * VAKOTODO + * @param queryVector + * @param path + * @param index + * @param numCandidates + * @param limit + * @param options + * @return + * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * @mongodb.server.release 7.1 + * @since 4.11 + */ + @Beta(Beta.Reason.SERVER) + public static Bson vectorSearch( + final FieldSearchPath path, + final Iterable queryVector, + final String index, + final long numCandidates, + final long limit, + final VectorSearchOptions options) { + notNull("path", path); + notNull("queryVector", queryVector); + notNull("index", index); + notNull("options", options); + return new Bson() { + @Override + public BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry) { + Document specificationDoc = new Document("path", path.toValue()) + .append("queryVector", queryVector) + .append("index", index) + .append("numCandidates", numCandidates) + .append("limit", limit); + specificationDoc.putAll(options.toBsonDocument(documentClass, codecRegistry)); + return new Document("$vectorSearch", specificationDoc).toBsonDocument(documentClass, codecRegistry); + } + + @Override + public String toString() { + return "Stage{name=$vectorSearch" + + ", field=" + queryVector + + ", path=" + path + + ", index=" + index + + ", numCandidates=" + numCandidates + + ", limit=" + limit + + ", options=" + options + + '}'; + } + }; + } + /** * Creates an $unset pipeline stage that removes/excludes fields from documents * diff --git a/driver-core/src/main/com/mongodb/client/model/Filters.java b/driver-core/src/main/com/mongodb/client/model/Filters.java index c516fe28930..a9fc527263e 100644 --- a/driver-core/src/main/com/mongodb/client/model/Filters.java +++ b/driver-core/src/main/com/mongodb/client/model/Filters.java @@ -61,8 +61,7 @@ private Filters() { } /** - * Creates a filter that matches all documents where the value of _id field equals the specified value. Note that this doesn't - * actually generate a $eq operator, as the query language doesn't require it. + * Creates a filter that matches all documents where the value of _id field equals the specified value. * * @param value the value, which may be null * @param the value type @@ -76,8 +75,7 @@ public static Bson eq(@Nullable final TItem value) { } /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this doesn't - * actually generate a $eq operator, as the query language doesn't require it. + * Creates a filter that matches all documents where the value of the field name equals the specified value. * * @param fieldName the field name * @param value the value, which may be null @@ -86,7 +84,7 @@ public static Bson eq(@Nullable final TItem value) { * @mongodb.driver.manual reference/operator/query/eq $eq */ public static Bson eq(final String fieldName, @Nullable final TItem value) { - return new SimpleEncodingFilter<>(fieldName, value); + return new OperatorFilter<>("$eq", fieldName, value); } /** diff --git a/driver-core/src/main/com/mongodb/client/model/Projections.java b/driver-core/src/main/com/mongodb/client/model/Projections.java index d9f282389c8..55f0bb69cd6 100644 --- a/driver-core/src/main/com/mongodb/client/model/Projections.java +++ b/driver-core/src/main/com/mongodb/client/model/Projections.java @@ -16,10 +16,12 @@ package com.mongodb.client.model; +import com.mongodb.client.model.search.FieldSearchPath; import com.mongodb.client.model.search.SearchCollector; import com.mongodb.client.model.search.SearchCount; import com.mongodb.client.model.search.SearchOperator; import com.mongodb.client.model.search.SearchOptions; +import com.mongodb.client.model.search.VectorSearchOptions; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; @@ -196,6 +198,20 @@ public static Bson metaSearchScore(final String fieldName) { return meta(fieldName, "searchScore"); } + /** + * Creates a projection to the given field name of the vectorSearchScore, + * for use with {@link Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions)}. + * Calling this method is equivalent to calling {@link #meta(String, String)} with {@code "vectorSearchScore"} as the second argument. + * + * @param fieldName the field name + * @return the projection + * @mongodb.atlas.manual atlas-search/scoring/ Scoring + * @since 4.7 + */ + public static Bson metaVectorSearchScore(final String fieldName) { + return meta(fieldName, "vectorSearchScore"); + } + /** * Creates a projection to the given field name of the searchHighlights, * for use with {@link Aggregates#search(SearchOperator, SearchOptions)} / {@link Aggregates#search(SearchCollector, SearchOptions)}. diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchConstructibleBson.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchConstructibleBson.java new file mode 100644 index 00000000000..b9a2f806744 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchConstructibleBson.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 com.mongodb.client.model.search; + +import com.mongodb.annotations.Immutable; +import com.mongodb.internal.client.model.AbstractConstructibleBson; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; + +import static com.mongodb.assertions.Assertions.notNull; + +final class VectorSearchConstructibleBson extends AbstractConstructibleBson implements VectorSearchOptions { + /** + * An {@linkplain Immutable immutable} {@link BsonDocument#isEmpty() empty} instance. + */ + static final VectorSearchConstructibleBson EMPTY_IMMUTABLE = new VectorSearchConstructibleBson(AbstractConstructibleBson.EMPTY_IMMUTABLE); + + VectorSearchConstructibleBson(final Bson base) { + super(base); + } + + private VectorSearchConstructibleBson(final Bson base, final Document appended) { + super(base, appended); + } + + @Override + protected VectorSearchConstructibleBson newSelf(final Bson base, final Document appended) { + return new VectorSearchConstructibleBson(base, appended); + } + + @Override + public VectorSearchOptions filter(final Bson filter) { + return newAppended("filter", notNull("name", filter)); + } + + @Override + public VectorSearchOptions option(final String name, final Object value) { + return newAppended(notNull("name", name), notNull("value", value)); + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java new file mode 100644 index 00000000000..15fef7716ce --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 com.mongodb.client.model.search; + +import com.mongodb.annotations.Beta; +import com.mongodb.annotations.Sealed; +import com.mongodb.client.model.Aggregates; +import org.bson.conversions.Bson; + +/** + * Represents optional fields of the {@code $vectorSearch} pipeline stage of an aggregation pipeline. + * + * @see Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions) + * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * @mongodb.server.release 7.1 + * @since 4.11 + */ +@Sealed +@Beta(Beta.Reason.SERVER) +public interface VectorSearchOptions extends Bson { + /** + * Creates a new {@link VectorSearchOptions} with the filter specified. + * + * @param filter VAKOTODO + * @return A new {@link VectorSearchOptions}. + */ + VectorSearchOptions filter(Bson filter); + + /** + * Creates a new {@link VectorSearchOptions} with the specified option in situations when there is no builder method + * that better satisfies your needs. + * This method cannot be used to validate the syntax. + *

+ * Example
+ * The following code creates two functionally equivalent {@link VectorSearchOptions} objects, + * though they may not be {@linkplain Object#equals(Object) equal}. + *

{@code
+     *  VectorSearchOptions options1 = VectorSearchOptions.vectorSearchOptions()
+     *      .filter(Filters.lt("fieldName", 1));
+     *  VectorSearchOptions options2 = VectorSearchOptions.vectorSearchOptions()
+     *      .option("filter", Filters.lt("fieldName", 1));
+     * }
+ * + * @param name The option name. + * @param value The option value. + * @return A new {@link VectorSearchOptions}. + */ + VectorSearchOptions option(String name, Object value); + + /** + * Returns {@link VectorSearchOptions} that represents server defaults. + * + * @return {@link VectorSearchOptions} that represents server defaults. + */ + static VectorSearchOptions vectorSearchOptions() { + return VectorSearchConstructibleBson.EMPTY_IMMUTABLE; + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/package-info.java b/driver-core/src/main/com/mongodb/client/model/search/package-info.java index 88dd10c5b19..d17cba4139e 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/package-info.java +++ b/driver-core/src/main/com/mongodb/client/model/search/package-info.java @@ -25,6 +25,7 @@ * * @see com.mongodb.client.model.Aggregates#search(SearchOperator, SearchOptions) * @see com.mongodb.client.model.Aggregates#search(SearchCollector, SearchOptions) + * @see com.mongodb.client.model.Aggregates#vectorSearch(FieldSearchPath, java.lang.Iterable, java.lang.String, long, long, VectorSearchOptions) * @mongodb.atlas.manual atlas-search/ Atlas Search * @mongodb.atlas.manual atlas-search/query-syntax/ Atlas Search aggregation pipeline stages * @since 4.7 diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java index dea209b566c..f7d9437a624 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java @@ -28,6 +28,7 @@ import org.bson.json.JsonWriterSettings; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -43,18 +44,31 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.mongodb.ClusterFixture.isAtlasSearchTest; +import static com.mongodb.ClusterFixture.serverVersionAtLeast; import static com.mongodb.client.model.Aggregates.limit; import static com.mongodb.client.model.Aggregates.project; import static com.mongodb.client.model.Aggregates.replaceWith; +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.gt; +import static com.mongodb.client.model.Filters.gte; +import static com.mongodb.client.model.Filters.in; +import static com.mongodb.client.model.Filters.lt; +import static com.mongodb.client.model.Filters.lte; +import static com.mongodb.client.model.Filters.ne; +import static com.mongodb.client.model.Filters.nin; +import static com.mongodb.client.model.Filters.or; import static com.mongodb.client.model.Projections.computedSearchMeta; import static com.mongodb.client.model.Projections.metaSearchHighlights; import static com.mongodb.client.model.Projections.metaSearchScore; +import static com.mongodb.client.model.Projections.metaVectorSearchScore; import static com.mongodb.client.model.search.FuzzySearchOptions.fuzzySearchOptions; import static com.mongodb.client.model.search.SearchCollector.facet; import static com.mongodb.client.model.search.SearchCount.lowerBound; @@ -84,10 +98,14 @@ import static com.mongodb.client.model.search.SearchScoreExpression.multiplyExpression; import static com.mongodb.client.model.search.SearchScoreExpression.pathExpression; import static com.mongodb.client.model.search.SearchScoreExpression.relevanceExpression; +import static com.mongodb.client.model.search.VectorSearchOptions.vectorSearchOptions; import static java.time.ZoneOffset.UTC; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -183,24 +201,105 @@ * } * } * + * + * {@code sample_mflix.embedded_movies} + * {@code sample_mflix__embedded_movies} + *
{@code
+ *            {
+ *              "mappings": {
+ *                "dynamic": true,
+ *                "fields": {
+ *                  "plot_embedding": {
+ *                    "dimensions": 1536,
+ *                    "similarity": "cosine",
+ *                    "type": "knnVector"
+ *                  }
+ *                }
+ *              }
+ *            }
+ *          }
+ * * * */ final class AggregatesSearchIntegrationTest { private static final MongoNamespace MFLIX_MOVIES_NS = new MongoNamespace("sample_mflix", "movies"); + private static final MongoNamespace MFLIX_EMBEDDED_MOVIES_NS = new MongoNamespace("sample_mflix", "embedded_movies"); private static final MongoNamespace AIRBNB_LISTINGS_AND_REVIEWS_NS = new MongoNamespace("sample_airbnb", "listingsAndReviews"); + private static final List QUERY_VECTOR = unmodifiableList(asList(-0.0072121937, -0.030757688, 0.014948666, -0.018497631, -0.019035352, 0.028149737, -0.0019593239, -0.02012424, -0.025649332, -0.007985169, 0.007830574, 0.023726976, -0.011507247, -0.022839734, 0.00027999343, -0.010431803, 0.03823202, -0.025756875, -0.02074262, -0.0042883316, -0.010841816, 0.010552791, 0.0015266258, -0.01791958, 0.018430416, -0.013980767, 0.017247427, -0.010525905, 0.0126230195, 0.009255537, 0.017153326, 0.008260751, -0.0036060968, -0.019210111, -0.0133287795, -0.011890373, -0.0030599732, -0.0002904958, -0.001310697, -0.020715732, 0.020890493, 0.012428096, 0.0015837587, -0.006644225, -0.028499257, -0.005098275, -0.0182691, 0.005760345, -0.0040665213, 0.00075491105, 0.007844017, 0.00040791242, 0.0006780336, 0.0027037326, -0.0041370974, -0.022275126, 0.004775642, -0.0045235846, -0.003659869, -0.0020567859, 0.021602973, 0.01010917, -0.011419867, 0.0043689897, -0.0017946466, 0.000101610516, -0.014061426, -0.002626435, -0.00035540052, 0.0062174085, 0.020809835, 0.0035220778, -0.0071046497, -0.005041142, 0.018067453, 0.012569248, -0.021683631, 0.020245226, 0.017247427, 0.017032338, 0.01037131, -0.036296222, -0.026334926, 0.041135717, 0.009625221, 0.032155763, -0.025057837, 0.027827105, -0.03323121, 0.0055721425, 0.005716655, 0.01791958, 0.012078577, -0.011117399, -0.0016005626, -0.0033254733, -0.007702865, 0.034306653, 0.0063854465, -0.009524398, 0.006069535, 0.012696956, -0.0042883316, -0.013167463, -0.0024667988, -0.02356566, 0.00052721944, -0.008858967, 0.039630096, -0.0064593833, -0.0016728189, -0.0020366213, 0.00622413, -0.03739855, 0.0028616884, -0.0102301575, 0.017717933, -0.0041068504, -0.0060896995, -0.01876649, 0.0069903834, 0.025595559, 0.029762903, -0.006388807, 0.017247427, 0.0022080203, -0.029117636, -0.029870447, -0.0049739266, -0.011809715, 0.023243025, 0.009510955, 0.030004878, 0.0015837587, -0.018524516, 0.007931396, -0.03589293, 0.013590919, -0.026361812, 0.002922182, 0.025743432, 0.014894894, 0.0012989342, -0.0016232478, 0.006251016, 0.029789789, -0.004664737, 0.017812036, -0.013436324, -0.0102301575, 0.016884465, -0.017220542, 0.010156221, 0.00014503786, 0.03933435, 0.018658947, 0.016897907, 0.0076961434, -0.029843561, -0.02021834, 0.015056211, 0.01002179, -0.0031994449, -0.03796316, -0.008133043, 0.03707592, 0.032128878, 9.483648E-05, 0.0017627194, -0.0007544909, 0.006647586, 0.020903936, -0.032559056, 0.025272924, -0.012804501, 0.019210111, 0.0022987607, 0.013301893, -0.0047218697, -0.022853177, -0.02162986, 0.006788738, 0.0092286505, 0.024184039, -0.015419173, -0.006479548, -0.00180977, 0.0060728956, -0.0030919004, 0.0022449887, -0.004046357, 0.012663349, -0.028579915, 0.0047722813, -0.6775295, -0.018779935, -0.018484188, -0.017449073, -0.01805401, 0.026630674, 0.008018777, 0.013436324, -0.0034683058, 0.00070912065, -0.005027699, 0.009658828, -0.0031792803, -0.010478854, 0.0034951917, -0.011594627, 0.02441257, -0.042533796, -0.012414653, 0.006261098, -0.012266779, 0.026630674, -0.017852364, -0.02184495, 0.02176429, 0.019263884, 0.00984031, -0.012609577, -0.01907568, -0.020231783, -0.002886894, 0.02706085, -0.0042345594, 0.02265153, 0.05769755, 0.021522315, -0.014195856, 0.011144285, 0.0038077426, 0.024573887, -0.03578539, -0.004476534, 0.016521502, -0.019815048, 0.00071836275, 0.008173372, 0.013436324, 0.021885278, -0.0147604635, -0.021777734, 0.0052595916, -0.011668564, -0.02356566, -0.0049974523, 0.03473683, -0.0255149, 0.012831387, -0.009658828, -0.0031036632, -0.001386314, -0.01385978, 0.008294359, -0.02512505, -0.0012308789, 0.008711093, 0.03610802, 0.016225755, 0.014034539, 0.0032431346, -0.017852364, 0.017906137, 0.005787231, -0.03514012, 0.017207097, -0.0019542826, -0.010189828, 0.010808208, -0.017408744, -0.0074944976, 0.011009854, 0.00887241, 0.009652107, -0.0062409337, 0.009766373, 0.009759651, -0.0020819916, -0.02599885, 0.0040665213, 0.016064439, -0.019035352, -0.013604362, 0.020231783, -0.025272924, -0.01196431, -0.01509654, 0.0010233518, -0.00869765, -0.01064017, 0.005249509, -0.036807057, 0.00054570363, 0.0021777733, -0.009302587, -0.00039362916, 0.011386259, 0.013382551, 0.03046194, 0.0032380936, 0.037801843, -0.036807057, -0.006244295, 0.002392862, -0.01346321, -0.008953068, -0.0025861058, -0.022853177, 0.018242212, -0.0031624765, 0.009880639, -0.0017341529, 0.0072054723, 0.014693249, 0.026630674, 0.008435511, -0.012562525, 0.011581183, -0.0028768117, -0.01059312, -0.027746446, 0.0077969665, 2.468059E-05, -0.011151006, 0.0152712995, -0.01761039, 0.023256468, 0.0076625356, 0.0026163526, -0.028795004, 0.0025877862, -0.017583502, -0.016588718, 0.017556617, 0.00075491105, 0.0075885993, -0.011722336, -0.010620005, -0.017274313, -0.008025498, -0.036376882, 0.009457182, -0.007265966, -0.0048663826, -0.00494368, 0.003616179, 0.0067820163, 0.0033775652, -0.016037554, 0.0043320213, -0.007978448, -0.012925488, 0.029413383, -0.00016583256, -0.018040568, 0.004180787, -0.011453475, -0.013886666, -0.0072121937, 0.006486269, 0.008005333, -0.01412864, -0.00061796, -0.025635887, -0.006630782, 0.02074262, -0.007192029, 0.03906549, -0.0030885397, -0.00088976155, -0.022033151, -0.008758144, 0.00049361185, 0.009342916, -0.014988995, -0.008704372, 0.014276514, -0.012300386, -0.0020063745, 0.030892119, -0.010532626, 0.019653732, 0.0028583275, 0.006163636, 0.0071517, -0.017489402, -0.008448954, -0.004352186, 0.013201071, 0.01090231, 0.0004110631, 0.03306989, 0.006916447, 0.002922182, 0.023888292, -0.009067334, 0.012434817, -0.051298663, 0.016279528, -0.02741037, 0.026227381, -0.005182294, 0.008153207, -0.026603786, 0.0045571923, 0.018067453, 0.038016934, 0.028042194, 0.0077431942, 0.015499831, -0.020298999, 0.0013123773, -0.021334114, -0.026281154, -0.0012720482, -0.0045571923, 0.006086339, 0.0028952959, -0.003041489, 0.007931396, -0.0005406625, -0.023444671, -0.0038715971, 0.0070374343, -0.0019979726, 0.024089938, 0.0020903936, -0.024210924, 0.007319738, -0.005995598, 0.032478396, 0.020998036, 0.01654839, 0.033876475, 0.025098165, 0.021132467, -0.017099554, -0.013516982, 0.01306664, 0.010525905, -0.02335057, -0.013543868, -0.03583916, 0.021172797, -0.033607613, -0.0036094578, -0.007911232, -0.0054578763, 0.013227956, 0.00993441, 0.025810648, 0.02255743, -0.013678298, 0.012273501, 0.00040497174, 0.0019072321, 0.0008170851, 0.01540573, 0.015580489, 0.005239427, 0.003989224, -0.013254843, 0.024708318, 0.0046680975, -0.034360424, -0.0041942303, 0.0077095865, -0.0053503322, -0.024399128, -0.02644247, 0.0062476555, 0.021885278, -0.0010922474, -0.014209299, 0.018295985, 0.0135640325, 0.0033842868, 0.0017812036, 0.004735313, 0.006486269, -0.008072549, 0.009551284, 0.007938119, 0.0101696635, 0.021750847, 0.014034539, 0.0071449787, -0.008448954, 0.010841816, -0.008274195, -0.014531932, -0.0024785616, 0.0018601815, 0.009564727, -0.011130841, -0.020581303, 0.012985982, 0.019976366, -0.030542599, -0.021818062, -0.018551402, -0.0092286505, -0.024385685, 0.0036901159, -0.0061367503, -0.00034048714, -0.007057599, -0.014558818, -0.022221355, 0.023377456, 0.026119838, -0.0008813597, 0.004520224, 0.0027843907, -0.022382671, 0.0018248934, 0.13313992, 0.013685021, -6.170148E-05, 0.015876237, 0.005417547, -0.008314524, -0.019169783, -0.016494617, 0.016844137, -0.0046412116, 0.024305027, -0.027827105, 0.023162367, 0.0143034, -0.0029893972, -0.014626034, -0.018215327, 0.0073264595, 0.024331912, -0.0070777633, -0.0004259765, -0.00042345593, -0.0034262962, -0.00423792, -0.016185427, -0.017946465, -5.9706024E-05, 0.016467731, -0.014773907, -0.022664975, -0.009322752, -0.027585128, 0.0020651878, -0.010532626, -0.010546069, 0.009174879, -0.0011098915, 0.026469355, 0.022006266, -0.013039754, 0.023458114, 0.005481402, -0.00050705485, -0.012092019, 0.0055990284, -0.007057599, -0.012266779, 0.03253217, 0.007071042, -0.01699201, 0.06597847, -0.013436324, 0.0070038266, -0.009981461, 0.024829306, 0.0067383265, 0.0056292755, 0.0018534599, -0.020057024, 0.011735778, 0.0025491375, -0.022194467, 0.0012468424, -0.0051621296, -0.018457301, -0.008509448, -0.011594627, -0.0152712995, -0.001858501, -0.014921781, -0.0056696045, -0.0066979975, -0.02008391, 0.0040093884, 0.032935463, -0.0032935461, -0.0074205613, -0.014088311, -0.0014762144, -0.011218221, 0.011984475, -0.01898158, -0.027208723, -0.008072549, 0.010942639, 0.0183632, 0.04148524, -0.0009922648, -0.017086111, 0.013483374, 0.019841935, 0.024264697, 0.011601348, -0.0077431942, -0.020258669, -0.005770427, 0.013429603, -0.011554297, -0.012831387, -1.4752561E-06, 0.011594627, -0.012683514, -0.012824666, 0.02180462, 0.011023297, 0.012468425, -0.0029860365, -0.0076289284, -0.021293784, 0.005068028, 0.017812036, 0.0007708746, -0.008684208, 0.0048126103, -0.0076558143, 0.019169783, -0.0076558143, 0.028579915, -0.011574462, -0.03196756, -0.0011334168, -0.030219967, 0.023901735, 0.014021097, -0.016776921, 0.0030045207, -0.0019257163, -0.023579102, 0.004197591, 0.00012497831, -0.016803807, 0.01915634, -0.010472132, -0.042130504, -0.038016934, -0.007702865, -0.0025861058, -0.010512462, -0.013537147, -0.013382551, -0.0036397045, 0.0053032814, 0.0046277684, -0.021952493, -0.016588718, -0.031886905, 0.0058208387, -0.00043689896, -0.01337583, 0.018349757, 0.015244413, 0.00900684, -0.017677605, 0.01523097, 0.010337702, -0.024426013, -0.021965936, -0.014182413, 0.008596827, 0.029628472, 0.058611676, -0.015446059, 0.021374442, -0.0095042335, 0.00091748784, 0.021132467, -0.011285436, -0.0035724894, -0.027907763, 0.027302826, 0.004184148, 0.026281154, -0.0026802071, -0.015163755, 0.005699851, 0.023122039, 0.0075415485, -0.020057024, -0.0109359175, -0.018309427, 0.017529732, 0.0020685487, -0.012441538, 0.0023239665, 0.012038247, -0.017543174, 0.029332725, 0.01399421, -0.0092488155, -1.0607403E-05, 0.019371428, -0.0315105, 0.023471557, -0.009430297, 0.00022097006, 0.013301893, -0.020110795, -0.0072928523, 0.007649093, 0.011547576, 0.026805433, -0.01461259, -0.018968137, -0.0104250815, 0.0005646079, 0.031456728, -0.0020147765, -0.024224367, 0.002431511, -0.019371428, -0.025017507, -0.02365976, -0.004318578, -0.04457714, 0.0029826758, -0.020473758, -0.016118212, -0.00068181445, -0.03446797, -0.020715732, -0.04256068, -0.013792564, 0.013873223, 0.011413146, -0.002419748, 0.0123877665, -0.0011115718, 0.007978448, 0.021441657, 0.004405958, 0.0042480025, 0.022920392, -0.0067920987, 0.011083791, -0.017529732, -0.03659197, -0.0066005355, -0.023888292, -0.016521502, 0.009591613, -0.0008590946, 0.013846337, -0.021092137, -0.012562525, -0.0028415236, 0.02882189, 5.3378342E-05, -0.006943333, -0.012226449, -0.035570297, -0.024547001, 0.022355784, -0.018416973, 0.014209299, 0.010035234, 0.0046916227, 0.009672271, -0.00067635323, -0.024815861, 0.0007049197, 0.0017055863, -0.0051251613, 0.0019391594, 0.027665788, -0.007306295, -0.013369109, 0.006308149, 0.009699157, 0.000940173, 0.024842748, 0.017220542, -0.0053032814, -0.008395182, 0.011359373, 0.013214514, 0.0062711807, 0.004110211, -0.019277327, -0.01412864, -0.009322752, 0.007124814, 0.0035119955, -0.024036165, -0.012831387, -0.006734966, -0.0019694061, -0.025367027, -0.006630782, 0.016010666, 0.0018534599, -0.0030717358, -0.017717933, 0.008489283, 0.010875423, -0.0028700903, 0.0121323485, 0.004930237, 0.009947853, -0.02992422, 0.021777734, 0.00015081417, 0.010344423, 0.0017543174, 0.006166997, -0.0015467904, 0.010089005, 0.0111711705, -0.010740994, -0.016965123, -0.006771934, 0.014464716, 0.007192029, -0.0006175399, -0.010855259, -0.003787578, 0.015647706, 0.01002179, -0.015378844, -0.01598378, 0.015741806, -0.0039119264, -0.008422068, 0.03253217, -0.019210111, -0.014975552, 0.0025810648, 0.0035556855, 8.449164E-05, -0.034172222, -0.006395529, -0.0036867552, 0.020769505, 0.009766373, -0.017543174, -0.013557311, 0.0031994449, -0.0014577302, 0.01832287, -0.009907524, -0.024654545, 0.0049940916, 0.016965123, 0.004476534, 0.022261683, -0.009369803, 0.0015308268, -0.010102449, -0.001209874, -0.023807634, -0.008348132, -0.020312442, 0.030892119, -0.0058309208, -0.005128522, -0.02437224, 0.01478735, -0.011016576, -0.010290652, -0.00503106, 0.016884465, 0.02132067, -0.014236185, -0.004903351, 0.01902191, 0.0028179984, 0.019505858, -0.021535758, -0.0038514326, 0.0112115, 0.0038682362, 0.003217929, -0.0012770894, -0.013685021, -0.008381739, 0.0025256122, 0.029386498, 0.018645504, 0.005323446, -0.0032784226, -0.0043253, 0.0007998612, 0.019949479, 0.025770318, -0.0030868594, 0.018968137, -0.010236879, -0.005370497, -0.024748646, -0.014047982, 0.005760345, -0.03610802, 0.0042009517, -0.0034817487, 0.003385967, 0.006560206, -0.006294706, -0.02400928, -0.006140111, -0.0017980073, -0.012481867, -0.0033960494, -0.00097210024, 0.014061426, -0.017596947, -0.023202697, 0.0028499255, -0.016010666, -0.028149737, 0.0024752007, -0.018941252, 0.0056158323, -0.012912045, 0.0054410724, 0.003054932, 0.019559631, -0.0048932685, -0.007823853, -0.017099554, 0.025662774, 0.02572999, 0.004379072, -0.010223436, 0.0031036632, -0.011755943, -0.025622444, -0.030623257, 0.019895706, -0.02052753, -0.006637504, -0.001231719, -0.013980767, -0.02706085, -0.012071854, -0.0041370974, -0.008885853, 0.0001885177, 0.2460615, -0.009389968, -0.010714107, 0.0326666, 0.0009561366, 0.022624645, 0.009793258, 0.019452088, -0.004493338, -0.007097928, -0.0022298652, 0.012401209, -0.0036229007, -0.00023819396, -0.017502844, -0.014209299, -0.030542599, -0.004863022, 0.005128522, -0.03081146, 0.02118624, -0.0042177555, 0.0032448152, -0.019936036, 0.015311629, 0.0070508774, -0.02021834, 0.0016148458, 0.04317906, 0.01385978, 0.004211034, -0.02534014, -0.00030309867, -0.011930703, -0.00207527, -0.021643303, 0.01575525, -0.0042883316, 0.0069231684, 0.017946465, 0.03081146, 0.0043857936, 3.646951E-05, -0.0214551, 0.0089933975, 0.022785962, -0.008106156, 0.00082884775, -0.0006717322, -0.0025457768, -0.017059224, -0.035113234, 0.054982055, 0.021266898, -0.0071046497, -0.012636462, 0.016965123, 0.01902191, -0.0061737187, 0.00076247274, 0.0002789432, 0.030112421, -0.0026768465, 0.0015207445, -0.004926876, 0.0067551304, -0.022624645, 0.0005003333, 0.0035523248, -0.0041337362, 0.011634956, -0.0183632, -0.02820351, -0.0061737187, -0.022355784, -0.03796316, 0.041888528, 0.019626847, 0.02211381, 0.001474534, 0.0037640526, 0.0085228905, 0.013140577, 0.012616298, -0.010599841, -0.022920392, 0.011278715, -0.011493804, -0.0044966987, -0.028741231, 0.015782135, -0.011500525, -0.00027621258, -0.0046378504, -0.003280103, 0.026993636, 0.0109359175, 0.027168395, 0.014370616, -0.011890373, -0.020648519, -0.03465617, 0.001964365, 0.034064677, -0.02162986, -0.01081493, 0.014397502, 0.008038941, 0.029789789, -0.012044969, 0.0038379894, -0.011245107, 0.0048193317, -0.0048563, 0.0142899575, 0.009779816, 0.0058510853, -0.026845763, 0.013281729, -0.0005818318, 0.009685714, -0.020231783, -0.004197591, 0.015593933, -0.016319858, -0.019492416, -0.008314524, 0.014693249, 0.013617805, -0.02917141, -0.0052058194, -0.0061838008, 0.0072726877, -0.010149499, -0.019035352, 0.0070374343, -0.0023138842, 0.0026583623, -0.00034111727, 0.0019038713, 0.025945077, -0.014693249, 0.009820145, -0.0037506097, 0.00041127318, -0.024909964, 0.008603549, -0.0041707046, 0.019398315, -0.024022723, -0.013409438, -0.027880875, 0.0023558936, -0.024237812, 0.034172222, -0.006251016, -0.048152987, -0.01523097, -0.002308843, -0.013691742, -0.02688609, 0.007810409, 0.011513968, -0.006647586, -0.011735778, 0.0017408744, -0.17422187, 0.01301959, 0.018860593, -0.00068013405, 0.008791751, -0.031618044, 0.017946465, 0.011735778, -0.03129541, 0.0033607613, 0.0072861305, 0.008227143, -0.018443858, -0.014007653, 0.009961297, 0.006284624, -0.024815861, 0.012676792, 0.014222742, 0.0036632298, 0.0028364826, -0.012320551, -0.0050478633, 0.011729057, 0.023135481, 0.025945077, 0.005676326, -0.007192029, 0.0015308268, -0.019492416, -0.008932903, -0.021737404, 0.012925488, 0.008092714, 0.03245151, -0.009457182, -0.018524516, 0.0025188907, -0.008569942, 0.0022769158, -0.004617686, 0.01315402, 0.024291582, -0.001880346, 0.0014274834, 0.04277577, 0.010216715, -0.018699275, 0.018645504, 0.008059106, 0.02997799, -0.021576088, 0.004846218, 0.015741806, 0.0023542133, 0.03142984, 0.01372535, 0.01598378, 0.001151901, -0.012246614, -0.004184148, -0.023605987, 0.008657321, -0.025770318, -0.019048795, -0.023054823, 0.005535174, -0.018161554, -0.019761277, 0.01385978, -0.016655933, 0.01416897, 0.015311629, 0.008919461, 0.0077499156, 0.023888292, 0.015257857, 0.009087498, 0.0017845642, 0.0013762318, -0.023713533, 0.027464142, -0.014021097, -0.024681432, -0.006741687, 0.0016450927, -0.005804035, -0.002821359, 0.0056796866, -0.023189254, 0.00723908, -0.013483374, -0.018390086, -0.018847149, 0.0061905226, 0.033365637, 0.008489283, 0.015257857, 0.019694062, -0.03019308, -0.012253336, 0.0021744126, -0.00754827, 0.01929077, 0.025044393, 0.017677605, 0.02503095, 0.028579915, 0.01774482, 0.0029961187, -0.019895706, 0.001165344, -0.0075281053, 0.02105181, -0.009221929, 0.023404341, -0.0028079161, -0.0037237236, 0.02847237, 0.0009821824, 0.04629785, -0.017771706, -0.038904175, 0.00869765, 0.0016249281, 0.020984594, -0.10867358, -0.008395182, -0.0010830053, 0.008059106, -0.020097353, 0.0020383017, 0.008038941, -0.009047169, -0.007252523, 0.0286068, -0.0037774958, -0.024923407, 0.005279756, -0.009524398, 0.011527412, -0.0020198175, 0.019452088, 0.014384058, -0.025609002, 0.006025845, -0.030542599, 0.016790364, 0.019223554, -0.012434817, 0.003901844, -0.007817131, -0.027612016, 0.008314524, 0.007938119, -0.0004868903, 0.014747021, -0.009457182, 0.014706692, -0.018847149, 0.015311629, 0.015647706, -0.0031288688, -0.0032717013, 0.008879132, -0.034629285, 0.0090337265, 0.004382433, 0.011305601, -0.028391711, 0.0053268066, 0.0003566608, -0.019169783, 0.011507247, 0.023592545, -0.006603896, -0.009685714, 0.010714107, -0.027907763, 0.006412333, 0.0045706355, -0.029816674, 0.0047958065, 0.0018500991, -0.011500525, 0.0030179636, 0.015997224, -0.022140697, -0.0001849469, -0.014263071, 0.011540854, -0.006607257, -0.01871272, -0.0038480717, -0.0024903242, -0.031214751, -0.0050478633, 0.021481987, -0.012912045, 0.028122852, -0.018605174, -0.00723908, 0.0023609349, -0.0073331813, 0.014935223, -0.005699851, -0.0068895607, -0.015244413, 0.029789789, -0.02458733, 0.0004453009, 0.0015577129, 0.0048596608, 0.009376524, -0.011984475, -0.014518489, 0.015647706, 0.0068794787, 0.0065534846, 0.003107024, -0.01973439, 0.027383484, -0.015459502, -0.006318231, 0.020863606, -0.0021357639, -0.0076692575, -0.021266898, -0.046862457, 0.025326697, 0.016521502, -0.0036833945, 0.0029860365, -0.016306413, 0.026496243, -0.016803807, 0.008724537, -0.0025407355, -0.027302826, 0.017798591, 0.0060796174, -0.014007653, -0.01650806, -0.0095042335, 0.009242094, -0.009342916, 0.010330981, 0.009544563, 0.018591732, 0.0036867552, 0.0194252, 0.0092488155, -0.007823853, 0.0015501512, -0.012031525, 0.010203271, -0.0074272826, -0.020258669, 0.025662774, -0.03032751, 0.014854565, 0.010835094, 0.0007708746, 0.0009989863, -0.014007653, -0.012871716, 0.023444671, 0.03323121, -0.034575514, -0.024291582, 0.011634956, -0.025958521, -0.01973439, 0.0029742739, 0.0067148013, 0.0022399474, 0.011802994, 0.011151006, -0.0116416775, 0.030166194, 0.013039754, -0.022517102, -0.011466918, -0.0033053088, 0.006156915, 0.004829414, 0.006029206, -0.016534945, 0.015325071, -0.0109359175, 0.032854803, -0.001010749, 0.0021155993, -0.011702171, -0.009766373, 0.00679882, 0.0040900465, -0.019438643, -0.006758491, -0.0040060277, 0.022436442, 0.025850976, 0.006150193, 0.018632062, -0.0077230297, -0.015298186, -0.017381858, 0.01911601, -0.005763706, -0.0022281848, -0.031994447, 0.0015972018, 0.028848775, 0.014572261, -0.0073264595, -0.009551284, -0.0052058194, 0.014518489, -0.0041068504, 0.010754436, 0.0055519775, -0.005804035, -0.0054007433, 0.028579915, -0.01791958, -0.015284742, 0.036807057, 0.015069654, -0.0023810994, -0.0038648755, 0.0015467904, -0.0037136413, 0.0023458113, 0.019008467, -0.011547576, -0.010001626, 0.012347437, 0.0155267175, 0.01907568, -0.003041489, -0.0132414, 0.017449073, 0.00060073606, -0.008536334, 0.008233866, -0.0085430555, -0.02365976, 0.024089938, -0.0034615842, -0.006580371, 0.008327967, -0.01509654, 0.009692436, 0.025635887, 0.0020282194, -0.04022159, -0.0021290423, -0.012407931, -0.0021727323, 0.006506434, -0.005320085, -0.008240587, 0.020984594, -0.014491603, 0.003592654, 0.0072121937, -0.03081146, 0.043770555, 0.009302587, -0.003217929, 0.019008467, -0.011271994, 0.02917141, 0.0019576435, -0.0077431942, -0.0030448497, -0.023726976, 0.023377456, -0.006382086, 0.025716545, -0.017341528, 0.0035556855, -0.019129453, -0.004311857, -0.003253217, -0.014935223, 0.0036363439, 0.018121226, -0.0066543072, 0.02458733, 0.0035691285, 0.0039085653, -0.014209299, 0.020191453, 0.0357585, 0.007830574, -0.024130266, -0.008912739, 0.008314524, -0.0346024, -0.0014005973, -0.006788738, -0.021777734, 0.010465411, -0.004012749, -0.00679882, 0.009981461, -0.026227381, 0.027033964, -0.015567047, -0.0063115098, 0.0023071626, 0.01037131, 0.015741806, -0.020635074, -0.012945653)); private static Map> collectionHelpers; @BeforeAll static void beforeAll() { collectionHelpers = new HashMap<>(); collectionHelpers.put(MFLIX_MOVIES_NS, new CollectionHelper<>(new BsonDocumentCodec(), MFLIX_MOVIES_NS)); + collectionHelpers.put(MFLIX_EMBEDDED_MOVIES_NS, new CollectionHelper<>(new BsonDocumentCodec(), MFLIX_EMBEDDED_MOVIES_NS)); collectionHelpers.put(AIRBNB_LISTINGS_AND_REVIEWS_NS, new CollectionHelper<>(new BsonDocumentCodec(), AIRBNB_LISTINGS_AND_REVIEWS_NS)); } @BeforeEach void beforeEach() { assumeTrue(isAtlasSearchTest()); + assumeTrue(serverVersionAtLeast(7, 1)); + } + + @Test + void vectorSearch() { + CollectionHelper collectionHelper = collectionHelpers.get(MFLIX_EMBEDDED_MOVIES_NS); + int limit = 2; + assertAll( + () -> { + List pipeline = singletonList( + Aggregates.vectorSearch( + // `multi` is used here only to verify that it is tolerated + fieldPath("plot_embedding").multi("ignored"), + QUERY_VECTOR, "sample_mflix__embedded_movies", limit + 1, limit) + ); + Asserters.size(limit) + .accept(collectionHelper.aggregate(pipeline), msgSupplier(pipeline)); + }, + () -> { + List pipeline = asList( + Aggregates.vectorSearch( + fieldPath("plot_embedding"), QUERY_VECTOR, "sample_mflix__embedded_movies", limit + 1, limit, + vectorSearchOptions().filter(gte("year", 2016))), + Aggregates.project( + metaVectorSearchScore("vectorSearchScore")) + ); + List results = collectionHelper.aggregate(pipeline); + Asserters.size(1) + .accept(results, msgSupplier(pipeline)); + Asserters.firstResult((doc, msgSupplier) -> + assertTrue(doc.getDouble("vectorSearchScore").doubleValue() > 0, msgSupplier)) + .accept(results, msgSupplier(pipeline)); + } + ); + } + + @Test + void vectorSearchSupportedFilters() { + CollectionHelper collectionHelper = collectionHelpers.get(MFLIX_EMBEDDED_MOVIES_NS); + Consumer asserter = filter -> { + List pipeline = singletonList( + Aggregates.vectorSearch( + fieldPath("plot_embedding"), QUERY_VECTOR, "sample_mflix__embedded_movies", 1, 1, + vectorSearchOptions().filter(filter)) + ); + Asserters.nonEmpty() + .accept(collectionHelper.aggregate(pipeline), msgSupplier(pipeline)); + }; + assertAll( + () -> asserter.accept(lt("year", 2016)), + () -> asserter.accept(lte("year", 2016)), + () -> asserter.accept(eq("year", 2016)), + () -> asserter.accept(gte("year", 2016)), + () -> asserter.accept(gt("year", 2015)), + () -> asserter.accept(ne("year", 2016)), + () -> asserter.accept(in("year", 2000, 2016)), + () -> asserter.accept(nin("year", 2000, 2016)), + () -> asserter.accept(and(gte("year", 2015), lte("year", 2016))), + () -> asserter.accept(or(eq("year", 2015), eq("year", 2016))) + ); } /** @@ -215,8 +314,8 @@ void beforeEach() { * */ @ParameterizedTest(name = "{index} {0}") - @MethodSource("args") - void test( + @MethodSource("searchAndSearchMetaArgs") + void searchAndSearchMeta( @SuppressWarnings("unused") final String testDescription, final CustomizableSearchStageCreator stageUnderTestCreator, final MongoNamespace ns, @@ -248,12 +347,7 @@ void test( List pipeline = new ArrayList<>(); pipeline.add(stageUnderTest); pipeline.addAll(accessory.postStages); - Supplier msgSupplier = () -> "For reference, the pipeline (" + pipeline.size() + " elements) used in the test is\n[\n" - + pipeline.stream() - .map(stage -> stage.toBsonDocument(BsonDocument.class, MongoClientSettings.getDefaultCodecRegistry())) - .map(doc -> doc.toJson(JsonWriterSettings.builder().indent(true).build())) - .collect(Collectors.joining(",\n")) - + "\n]\n"; + Supplier msgSupplier = msgSupplier(pipeline); List results; try { results = collectionHelpers.get(ns).aggregate(pipeline); @@ -265,9 +359,9 @@ void test( } /** - * @see #test(String, CustomizableSearchStageCreator, MongoNamespace, List) + * @see #searchAndSearchMeta(String, CustomizableSearchStageCreator, MongoNamespace, List) */ - private static Stream args() { + private static Stream searchAndSearchMetaArgs() { return Stream.of( arguments( "default options", @@ -292,7 +386,7 @@ private static Stream args() { "`index`, `count` options", stageCreator( // `multi` is used here only to verify that it is tolerated - exists(fieldPath("title").multi("keyword")), + exists(fieldPath("title").multi("ignored")), searchOptions() .option("index", "default") .count(lowerBound().threshold(2_000)) @@ -358,7 +452,7 @@ private static Stream args() { arguments( "alternate analyzer (`multi` field path)", stageCreator( - text(singleton(fieldPath("title").multi("keyword")), singleton("The Cheat")), + text(singleton(fieldPath("title").multi("keyword")), singleton("Top Gun")), searchOptions().count(total()) ), MFLIX_MOVIES_NS, @@ -366,7 +460,7 @@ private static Stream args() { new Accessory( emptyList(), Asserters.firstResult((doc, msgSupplier) -> assertEquals( - "The Cheat", doc.getString("title").getValue(), msgSupplier)) + "Top Gun", doc.getString("title").getValue(), msgSupplier)) ), new Accessory( emptyList(), @@ -487,7 +581,7 @@ private static Stream args() { .maxExpansions(3)), autocomplete(fieldPath("title") // `multi` is used here only to verify that it is tolerated - .multi("keyword"), "term4"), + .multi("ignored"), "term4"), // this operator produces non-empty search results autocomplete(fieldPath("title"), "Traffic in", "term5") .fuzzy() @@ -542,6 +636,15 @@ private static Stream args() { ); } + private static Supplier msgSupplier(final Collection pipeline) { + return () -> "For reference, the pipeline (" + pipeline.size() + " elements) used in the test is\n[\n" + + pipeline.stream() + .map(stage -> stage.toBsonDocument(BsonDocument.class, MongoClientSettings.getDefaultCodecRegistry())) + .map(doc -> doc.toJson(JsonWriterSettings.builder().indent(true).build())) + .collect(Collectors.joining(",\n")) + + "\n]\n"; + } + private static final class Asserters { static Asserter empty() { return decorate((results, msgSupplier) -> assertTrue(results.isEmpty(), msgSupplier)); @@ -551,6 +654,10 @@ static Asserter nonEmpty() { return decorate((results, msgSupplier) -> assertFalse(results.isEmpty(), msgSupplier)); } + static Asserter size(final int expectedSize) { + return decorate((results, msgSupplier) -> assertEquals(expectedSize, results.size(), msgSupplier)); + } + /** * Checks the value of the {@code "score"} field for each result document. */ diff --git a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy index 10b6c60f501..9bcdb80b491 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy @@ -76,6 +76,7 @@ import static com.mongodb.client.model.Aggregates.sort import static com.mongodb.client.model.Aggregates.sortByCount import static com.mongodb.client.model.Aggregates.unionWith import static com.mongodb.client.model.Aggregates.unwind +import static com.mongodb.client.model.Aggregates.vectorSearch import static com.mongodb.client.model.BsonHelper.toBson import static com.mongodb.client.model.Filters.eq import static com.mongodb.client.model.Filters.expr @@ -97,6 +98,7 @@ import static com.mongodb.client.model.search.SearchOperator.exists import static com.mongodb.client.model.search.SearchOptions.searchOptions import static com.mongodb.client.model.search.SearchPath.fieldPath import static com.mongodb.client.model.search.SearchPath.wildcardPath +import static com.mongodb.client.model.search.VectorSearchOptions.vectorSearchOptions import static java.util.Arrays.asList import static org.bson.BsonDocument.parse @@ -241,7 +243,7 @@ class AggregatesSpecification extends Specification { def 'should render $match'() { expect: - toBson(match(eq('author', 'dave'))) == parse('{ $match : { author : "dave" } }') + toBson(match(eq('author', 'dave'))) == parse('{ $match : { author : { $eq: "dave" } } }') } def 'should render $project'() { @@ -314,7 +316,7 @@ class AggregatesSpecification extends Specification { parse('''{$facet: { "Screen Sizes": [ {$unwind: "$attributes"}, - {$match: {"attributes.name": "screen size"}}, + {$match: {"attributes.name": {$eq: "screen size"}}}, {$group: { _id: null, count: {$sum: 1} @@ -322,7 +324,7 @@ class AggregatesSpecification extends Specification { ], "Manufacturer": [ - {$match: {"attributes.name": "manufacturer"}}, + {$match: {"attributes.name": {$eq: "manufacturer"}}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, {$sort: {count: -1}} {$limit: 5} @@ -351,7 +353,7 @@ class AggregatesSpecification extends Specification { toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() .restrictSearchWithMatch(eq('hobbies', 'golf')))) == parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name", - as: "socialNetwork", restrictSearchWithMatch : { "hobbies" : "golf" } } }''') + as: "socialNetwork", restrictSearchWithMatch : { "hobbies" : { $eq: "golf" } } } }''') // with maxDepth and depthField toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() @@ -363,7 +365,7 @@ class AggregatesSpecification extends Specification { toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() .maxDepth(1).depthField('depth').restrictSearchWithMatch(eq('hobbies', 'golf')))) == parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name", - as: "socialNetwork", maxDepth: 1, depthField: "depth", restrictSearchWithMatch : { "hobbies" : "golf" } } }''') + as: "socialNetwork", maxDepth: 1, depthField: "depth", restrictSearchWithMatch : { "hobbies" : { $eq: "golf" } } } }''') } def 'should render $skip'() { @@ -847,6 +849,58 @@ class AggregatesSpecification extends Specification { }''') } + def 'should render $vectorSearch'() { + when: + BsonDocument vectorSearchDoc = toBson( + vectorSearch( + fieldPath('fieldName').multi('ignored'), + [1.0d, 2.0d], + 'indexName', + 2, + 1, + vectorSearchOptions() + .filter(Filters.ne("fieldName", "fieldValue")) + + ) + ) + + then: + vectorSearchDoc == parse('''{ + "$vectorSearch": { + "path": "fieldName", + "queryVector": [1.0, 2.0], + "index": "indexName", + "numCandidates": {"$numberLong": "2"}, + "limit": {"$numberLong": "1"}, + "filter": {"fieldName": {"$ne": "fieldValue"}} + } + }''') + } + + def 'should render $vectorSearch with no options'() { + when: + BsonDocument vectorSearchDoc = toBson( + vectorSearch( + fieldPath('fieldName'), + [1.0d, 2.0d], + 'indexName', + 2, + 1 + ) + ) + + then: + vectorSearchDoc == parse('''{ + "$vectorSearch": { + "path": "fieldName", + "queryVector": [1.0, 2.0], + "index": "indexName", + "numCandidates": {"$numberLong": "2"}, + "limit": {"$numberLong": "1"} + } + }''') + } + def 'should create string representation for simple stages'() { expect: match(new BsonDocument('x', new BsonInt32(1))).toString() == 'Stage{name=\'$match\', value={"x": 1}}' diff --git a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy index 9c9a4bc8748..1e0a09ae085 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy @@ -72,9 +72,9 @@ class FiltersSpecification extends Specification { def 'eq should render without $eq'() { expect: - toBson(eq('x', 1)) == parse('{x : 1}') - toBson(eq('x', null)) == parse('{x : null}') - toBson(eq(1)) == parse('{_id : 1}') + toBson(eq('x', 1)) == parse('{x : {$eq: 1}}') + toBson(eq('x', null)) == parse('{x : {$eq: null}}') + toBson(eq(1)) == parse('{_id : {$eq: 1}}') } def 'should render $ne'() { @@ -89,13 +89,13 @@ class FiltersSpecification extends Specification { toBson(not(gt('x', 1))) == parse('{x : {$not: {$gt: 1}}}') toBson(not(regex('x', '^p.*'))) == parse('{x : {$not: /^p.*/}}') - toBson(not(and(gt('x', 1), eq('y', 20)))) == parse('{$not: {$and: [{x: {$gt: 1}}, {y: 20}]}}') - toBson(not(and(eq('x', 1), eq('x', 2)))) == parse('{$not: {$and: [{x: 1}, {x: 2}]}}') - toBson(not(and(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$and: [{x: {$in: [1, 2]}}, {x: 3}]}}') + toBson(not(and(gt('x', 1), eq('y', 20)))) == parse('{$not: {$and: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}') + toBson(not(and(eq('x', 1), eq('x', 2)))) == parse('{$not: {$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}') + toBson(not(and(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$and: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}') - toBson(not(or(gt('x', 1), eq('y', 20)))) == parse('{$not: {$or: [{x: {$gt: 1}}, {y: 20}]}}') - toBson(not(or(eq('x', 1), eq('x', 2)))) == parse('{$not: {$or: [{x: 1}, {x: 2}]}}') - toBson(not(or(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$or: [{x: {$in: [1, 2]}}, {x: 3}]}}') + toBson(not(or(gt('x', 1), eq('y', 20)))) == parse('{$not: {$or: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}') + toBson(not(or(eq('x', 1), eq('x', 2)))) == parse('{$not: {$or: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}') + toBson(not(or(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$or: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}') toBson(not(parse('{$in: [1]}'))) == parse('{$not: {$in: [1]}}') @@ -106,8 +106,8 @@ class FiltersSpecification extends Specification { def 'should render $nor'() { expect: - toBson(nor(eq('price', 1))) == parse('{$nor : [{price: 1}]}') - toBson(nor(eq('price', 1), eq('sale', true))) == parse('{$nor : [{price: 1}, {sale: true}]}') + toBson(nor(eq('price', 1))) == parse('{$nor : [{price: {$eq: 1}}]}') + toBson(nor(eq('price', 1), eq('sale', true))) == parse('{$nor : [{price: {$eq: 1}}, {sale: {$eq: true}}]}') } def 'should render $gt'() { @@ -144,8 +144,8 @@ class FiltersSpecification extends Specification { def 'should render $or'() { expect: - toBson(or([eq('x', 1), eq('y', 2)])) == parse('{$or : [{x : 1}, {y : 2}]}') - toBson(or(eq('x', 1), eq('y', 2))) == parse('{$or : [{x : 1}, {y : 2}]}') + toBson(or([eq('x', 1), eq('y', 2)])) == parse('{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}') + toBson(or(eq('x', 1), eq('y', 2))) == parse('{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}') } def 'and should render empty and using $and'() { @@ -156,21 +156,21 @@ class FiltersSpecification extends Specification { def 'and should render using $and'() { expect: - toBson(and([eq('x', 1), eq('y', 2)])) == parse('{$and: [{x : 1}, {y : 2}]}') - toBson(and(eq('x', 1), eq('y', 2))) == parse('{$and: [{x : 1}, {y : 2}]}') + toBson(and([eq('x', 1), eq('y', 2)])) == parse('{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}') + toBson(and(eq('x', 1), eq('y', 2))) == parse('{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}') } def 'and should render $and with clashing keys'() { expect: - toBson(and([eq('a', 1), eq('a', 2)])) == parse('{$and: [{a: 1}, {a: 2}]}') + toBson(and([eq('a', 1), eq('a', 2)])) == parse('{$and: [{a: {$eq: 1}}, {a: {$eq: 2}}]}') } def 'and should not flatten nested'() { expect: toBson(and([and([eq('a', 1), eq('b', 2)]), eq('c', 3)])) == - parse('{$and: [{$and: [{a : 1}, {b : 2}]}, {c : 3}]}') + parse('{$and: [{$and: [{a : {$eq: 1}}, {b : {$eq: 2}}]}, {c : {$eq: 3}}]}') toBson(and([and([eq('a', 1), eq('a', 2)]), eq('c', 3)])) == - parse('{$and:[{$and: [{a : 1}, {a : 2}]}, {c : 3}]} }') + parse('{$and:[{$and: [{a : {$eq: 1}}, {a : {$eq: 2}}]}, {c : {$eq: 3}}]} }') toBson(and([lt('a', 1), lt('b', 2)])) == parse('{$and: [{a : {$lt : 1}}, {b : {$lt : 2}}]}') toBson(and([lt('a', 1), lt('a', 2)])) == @@ -224,7 +224,7 @@ class FiltersSpecification extends Specification { parse('{results : {$elemMatch : {$gte: 80, $lt: 85}}}') toBson(elemMatch('results', and(eq('product', 'xyz'), gt('score', 8)))) == - parse('{ results : {$elemMatch : {$and: [{product : "xyz"}, {score : {$gt : 8}}]}}}') + parse('{ results : {$elemMatch : {$and: [{product : {$eq: "xyz"}}, {score : {$gt : 8}}]}}}') } def 'should render $in'() { @@ -668,17 +668,17 @@ class FiltersSpecification extends Specification { def 'should render with iterable value'() { expect: toBson(eq('x', new Document())) == parse('''{ - x : {} + x : {$eq: {}} }''') toBson(eq('x', [1, 2, 3])) == parse('''{ - x : [1, 2, 3] + x : {$eq: [1, 2, 3]} }''') } def 'should create string representation for simple filter'() { expect: - eq('x', 1).toString() == 'Filter{fieldName=\'x\', value=1}' + eq('x', 1).toString() == 'Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' } def 'should create string representation for regex filter'() { @@ -694,10 +694,16 @@ class FiltersSpecification extends Specification { def 'should create string representation for compound filters'() { expect: - and(eq('x', 1), eq('y', 2)).toString() == 'And Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' - or(eq('x', 1), eq('y', 2)).toString() == 'Or Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' - nor(eq('x', 1), eq('y', 2)).toString() == 'Nor Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' - not(eq('x', 1)).toString() == 'Not Filter{filter=Filter{fieldName=\'x\', value=1}}' + and(eq('x', 1), eq('y', 2)).toString() == + 'And Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + + ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' + or(eq('x', 1), eq('y', 2)).toString() == + 'Or Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + + ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' + nor(eq('x', 1), eq('y', 2)).toString() == + 'Nor Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + + ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' + not(eq('x', 1)).toString() == 'Not Filter{filter=Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}}' } def 'should create string representation for geo filters'() { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy index 6e7d0336037..fea79ab2ceb 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy @@ -63,7 +63,7 @@ class ProjectionsSpecification extends Specification { def 'elemMatch'() { expect: toBson(elemMatch('x', and(eq('y', 1), eq('z', 2)))) == - parse('{x : {$elemMatch : {$and: [{y : 1}, {z : 2}]}}}') + parse('{x : {$elemMatch : {$and: [{y : {$eq: 1}}, {z : {$eq: 2}}]}}}') } def 'slice'() { @@ -111,7 +111,8 @@ class ProjectionsSpecification extends Specification { expect: elemMatch('x', and(eq('y', 1), eq('z', 2))).toString() == 'ElemMatch Projection{fieldName=\'x\', ' + - 'filter=And Filter{filters=[Filter{fieldName=\'y\', value=1}, Filter{fieldName=\'z\', value=2}]}}' + 'filter=And Filter{filters=[Operator Filter{fieldName=\'y\', operator=\'$eq\', value=1}' + + ', Operator Filter{fieldName=\'z\', operator=\'$eq\', value=2}]}}' } def 'should create string representation for fields'() { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java b/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java new file mode 100644 index 00000000000..4d3ad463b13 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/client/model/search/VectorSearchOptionsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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 com.mongodb.client.model.search; + +import com.mongodb.client.model.Filters; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class VectorSearchOptionsTest { + @Test + void vectorSearchOptions() { + assertEquals( + new BsonDocument(), + VectorSearchOptions.vectorSearchOptions() + .toBsonDocument() + ); + } + + @Test + void option() { + assertEquals( + VectorSearchOptions.vectorSearchOptions() + .filter(Filters.lt("fieldName", 1)) + .toBsonDocument(), + VectorSearchOptions.vectorSearchOptions() + .option("filter", Filters.lt("fieldName", 1)) + .toBsonDocument()); + } + + @Test + void filter() { + assertEquals( + new BsonDocument() + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()), + VectorSearchOptions.vectorSearchOptions() + .filter(Filters.lt("fieldName", 1)) + .toBsonDocument() + ); + } + + @Test + void options() { + assertEquals( + new BsonDocument() + .append("name", new BsonString("value")) + .append("filter", Filters.lt("fieldName", 1).toBsonDocument()), + VectorSearchOptions.vectorSearchOptions() + .option("name", "value") + .filter(Filters.lt("fieldName", 0)) + .option("filter", Filters.lt("fieldName", 1)) + .toBsonDocument() + ); + } + + @Test + void vectorSearchOptionsIsUnmodifiable() { + String expected = VectorSearchOptions.vectorSearchOptions().toBsonDocument().toJson(); + VectorSearchOptions.vectorSearchOptions().option("name", "value"); + assertEquals(expected, VectorSearchOptions.vectorSearchOptions().toBsonDocument().toJson()); + } + + @Test + void vectorSearchOptionsIsImmutable() { + String expected = VectorSearchOptions.vectorSearchOptions().toBsonDocument().toJson(); + VectorSearchOptions.vectorSearchOptions().toBsonDocument().append("name", new BsonString("value")); + assertEquals(expected, VectorSearchOptions.vectorSearchOptions().toBsonDocument().toJson()); + } +} diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala index 6f39034c1f9..99248240645 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala @@ -16,7 +16,9 @@ package org.mongodb.scala.model +import com.mongodb.annotations.Beta import com.mongodb.client.model.fill.FillOutputField +import com.mongodb.client.model.search.FieldSearchPath import scala.collection.JavaConverters._ import com.mongodb.client.model.{ Aggregates => JAggregates } @@ -25,7 +27,7 @@ import org.mongodb.scala.bson.conversions.Bson import org.mongodb.scala.model.densify.{ DensifyOptions, DensifyRange } import org.mongodb.scala.model.fill.FillOptions import org.mongodb.scala.model.geojson.Point -import org.mongodb.scala.model.search.{ SearchCollector, SearchOperator, SearchOptions } +import org.mongodb.scala.model.search.{ SearchCollector, SearchOperator, SearchOptions, VectorSearchOptions } /** * Builders for aggregation pipeline stages. @@ -720,6 +722,54 @@ object Aggregates { def searchMeta(collector: SearchCollector, options: SearchOptions): Bson = JAggregates.searchMeta(collector, options) + /** + * VAKOTODO + * + * @param queryVector + * @param path + * @param index + * @param numCandidates + * @param limit + * @return + * @see [[VAKOTODO \$vectorSearch]] + * @note Requires MongoDB 7.1 or greater + * @since 4.11 + */ + @Beta(Array(Beta.Reason.SERVER)) + def vectorSearch( + path: FieldSearchPath, + queryVector: Iterable[java.lang.Double], + index: String, + numCandidates: Long, + limit: Long + ): Bson = + JAggregates.vectorSearch(path, queryVector.asJava, index, numCandidates, limit) + + /** + * VAKOTODO + * + * @param queryVector + * @param path + * @param index + * @param numCandidates + * @param limit + * @param options + * @return + * @see [[VAKOTODO \$vectorSearch]] + * @note Requires MongoDB 7.1 or greater + * @since 4.11 + */ + @Beta(Array(Beta.Reason.SERVER)) + def vectorSearch( + path: FieldSearchPath, + queryVector: Iterable[java.lang.Double], + index: String, + numCandidates: Long, + limit: Long, + options: VectorSearchOptions + ): Bson = + JAggregates.vectorSearch(path, queryVector.asJava, index, numCandidates, limit, options) + /** * Creates an `\$unset` pipeline stage that removes/excludes fields from documents * diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala index cff938d6842..8d464d5bc91 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala @@ -38,8 +38,7 @@ import org.mongodb.scala.bson.conversions.Bson object Filters { /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this doesn't - * actually generate a `\$eq` operator, as the query language doesn't require it. + * Creates a filter that matches all documents where the value of the field name equals the specified value. * * @param fieldName the field name * @param value the value @@ -61,8 +60,7 @@ object Filters { def expr[TExpression](expression: TExpression): Bson = JFilters.expr(expression) /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this does - * actually generate a `\$eq` operator, as the query language doesn't require it. + * Creates a filter that matches all documents where the value of the field name equals the specified value. * * A friendly alias for the `eq` method. * diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala new file mode 100644 index 00000000000..109bb8e42a6 --- /dev/null +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * 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.mongodb.scala.model.search + +import com.mongodb.annotations.Beta +import com.mongodb.client.model.search.{ VectorSearchOptions => JVectorSearchOptions } + +/** + * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. + * + * @see [[VAKOTODO \$vectorSearch syntax]] + * @note Requires MongoDB 7.1 or greater + * @since 4.11 + */ +@Beta(Array(Beta.Reason.SERVER)) +object VectorSearchOptions { + + /** + * Returns `VectorSearchOptions` that represents server defaults. + * + * @return `VectorSearchOptions` that represents server defaults. + */ + def vectorSearchOptions(): VectorSearchOptions = JVectorSearchOptions.vectorSearchOptions() +} diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala index 103f6445e74..acd8a226d89 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala @@ -27,6 +27,7 @@ import com.mongodb.annotations.{ Beta, Sealed } * on which they are called. This allows storing and using such instances as templates. * * @see `Aggregates.search` + * @see `Aggregates.vectorSearch` * @see [[https://www.mongodb.com/docs/atlas/atlas-search/ Atlas Search]] * @see [[https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/ Atlas Search aggregation pipeline stages]] * @since 4.7 @@ -218,6 +219,15 @@ package object search { @Beta(Array(Beta.Reason.CLIENT)) type SearchOptions = com.mongodb.client.model.search.SearchOptions + /** + * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. + * + * @see [[VAKOTODO \$vectorSearch syntax]] + */ + @Sealed + @Beta(Array(Beta.Reason.SERVER)) + type VectorSearchOptions = com.mongodb.client.model.search.VectorSearchOptions + /** * Highlighting options. * You may use the `\$meta: "searchHighlights"` expression, e.g., via [[Projections.metaSearchHighlights]], diff --git a/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala index da57e569c66..390a2631343 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala @@ -41,6 +41,7 @@ import org.mongodb.scala.model.search.SearchCollector import org.mongodb.scala.model.search.SearchOperator.exists import org.mongodb.scala.model.search.SearchOptions.searchOptions import org.mongodb.scala.model.search.SearchPath.{ fieldPath, wildcardPath } +import org.mongodb.scala.model.search.VectorSearchOptions.vectorSearchOptions import org.mongodb.scala.{ BaseSpec, MongoClient, MongoNamespace } class AggregatesSpec extends BaseSpec { @@ -119,8 +120,10 @@ class AggregatesSpec extends BaseSpec { } it should "render $match" in { - toBson(`match`(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : "dave" } }""")) - toBson(filter(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : "dave" } }""")) + toBson(`match`(Filters.eq("author", "dave"))) should equal( + Document("""{ $match : { author : { $eq: "dave" } } }""") + ) + toBson(filter(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : { $eq: "dave" } } }""")) } it should "render $facet" in { @@ -142,9 +145,9 @@ class AggregatesSpec extends BaseSpec { ) ) should equal( Document( - """{$facet: { "Screen Sizes": [{$unwind: "$attributes"}, {$match: {"attributes.name": "screen size"}}, + """{$facet: { "Screen Sizes": [{$unwind: "$attributes"}, {$match: {"attributes.name": {$eq: "screen size"}}}, {$group: { _id: null, count: {$sum: 1} }}], - "Manufacturer": [ {$match: {"attributes.name": "manufacturer"}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, + "Manufacturer": [ {$match: {"attributes.name": {$eq: "manufacturer"}}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, {$sort: {count: -1}}, {$limit: 5}]}}""" ) ) @@ -762,6 +765,57 @@ class AggregatesSpec extends BaseSpec { ) } + it should "render $vectorSearch" in { + toBson( + Aggregates.vectorSearch( + fieldPath("fieldName").multi("ignored"), + List(1.0d, 2.0d), + "indexName", + 2, + 1, + vectorSearchOptions() + .filter(Filters.ne("fieldName", "fieldValue")) + ) + ) should equal( + Document( + """{ + "$vectorSearch": { + "path": "fieldName", + "queryVector": [1.0, 2.0], + "index": "indexName", + "numCandidates": {"$numberLong": "2"}, + "limit": {"$numberLong": "1"}, + "filter": {"fieldName": {"$ne": "fieldValue"}} + } + }""" + ) + ) + } + + it should "render $vectorSearch with no options" in { + toBson( + Aggregates.vectorSearch( + fieldPath("fieldName").multi("ignored"), + List(1.0d, 2.0d), + "indexName", + 2, + 1 + ) + ) should equal( + Document( + """{ + "$vectorSearch": { + "path": "fieldName", + "queryVector": [1.0, 2.0], + "index": "indexName", + "numCandidates": {"$numberLong": "2"}, + "limit": {"$numberLong": "1"} + } + }""" + ) + ) + } + it should "render $unset" in { toBson( Aggregates.unset("title", "author.first") diff --git a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala index 52a7b4254c1..a2d4b2a4935 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala @@ -46,11 +46,11 @@ class FiltersSpec extends BaseSpec { } it should "render without $eq" in { - toBson(model.Filters.eq("x", 1)) should equal(Document("""{x : 1}""")) - toBson(model.Filters.eq("x", null)) should equal(Document("""{x : null}""")) + toBson(model.Filters.eq("x", 1)) should equal(Document("""{x : {$eq: 1}}""")) + toBson(model.Filters.eq("x", null)) should equal(Document("""{x : {$eq: null}}""")) - toBson(model.Filters.equal("x", 1)) should equal(Document("""{x : 1}""")) - toBson(model.Filters.equal("x", null)) should equal(Document("""{x : null}""")) + toBson(model.Filters.equal("x", 1)) should equal(Document("""{x : {$eq: 1}}""")) + toBson(model.Filters.equal("x", null)) should equal(Document("""{x : {$eq: null}}""")) } it should "render $ne" in { @@ -63,30 +63,30 @@ class FiltersSpec extends BaseSpec { toBson(model.Filters.not(model.Filters.gt("x", 1))) should equal(Document("""{x : {$not: {$gt: 1}}}""")) toBson(model.Filters.not(model.Filters.regex("x", "^p.*"))) should equal(Document("""{x : {$not: /^p.*/}}""")) toBson(model.Filters.not(model.Filters.and(model.Filters.gt("x", 1), model.Filters.eq("y", 20)))) should equal( - Document("""{$not: {$and: [{x: {$gt: 1}}, {y: 20}]}}""") + Document("""{$not: {$and: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}""") ) toBson(model.Filters.not(model.Filters.and(model.Filters.eq("x", 1), model.Filters.eq("x", 2)))) should equal( - Document("""{$not: {$and: [{x: 1}, {x: 2}]}}""") + Document("""{$not: {$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}""") ) toBson(model.Filters.not(model.Filters.and(model.Filters.in("x", 1, 2), model.Filters.eq("x", 3)))) should equal( - Document("""{$not: {$and: [{x: {$in: [1, 2]}}, {x: 3}]}}""") + Document("""{$not: {$and: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.gt("x", 1), model.Filters.eq("y", 20)))) should equal( - Document("""{$not: {$or: [{x: {$gt: 1}}, {y: 20}]}}""") + Document("""{$not: {$or: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.eq("x", 1), model.Filters.eq("x", 2)))) should equal( - Document("""{$not: {$or: [{x: 1}, {x: 2}]}}""") + Document("""{$not: {$or: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.in("x", 1, 2), model.Filters.eq("x", 3)))) should equal( - Document("""{$not: {$or: [{x: {$in: [1, 2]}}, {x: 3}]}}""") + Document("""{$not: {$or: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}""") ) toBson(model.Filters.not(Document("$in" -> List(1)))) should equal(Document("""{$not: {$in: [1]}}""")) } it should "render $nor" in { - toBson(model.Filters.nor(model.Filters.eq("price", 1))) should equal(Document("""{$nor : [{price: 1}]}""")) + toBson(model.Filters.nor(model.Filters.eq("price", 1))) should equal(Document("""{$nor : [{price: {$eq: 1}}]}""")) toBson(model.Filters.nor(model.Filters.eq("price", 1), model.Filters.eq("sale", true))) should equal( - Document("""{$nor : [{price: 1}, {sale: true}]}""") + Document("""{$nor : [{price: {$eq: 1}}, {sale: {$eq: true}}]}""") ) } @@ -117,7 +117,7 @@ class FiltersSpec extends BaseSpec { it should "render $or" in { toBson(model.Filters.or(model.Filters.eq("x", 1), model.Filters.eq("y", 2))) should equal( - Document("""{$or : [{x : 1}, {y : 2}]}""") + Document("""{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}""") ) } @@ -127,7 +127,7 @@ class FiltersSpec extends BaseSpec { it should "and should render using $and" in { toBson(model.Filters.and(model.Filters.eq("x", 1), model.Filters.eq("y", 2))) should equal( - Document("""{$and: [{x : 1}, {y : 2}]}""") + Document("""{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}""") ) } @@ -140,10 +140,10 @@ class FiltersSpec extends BaseSpec { it should "and should flatten nested" in { toBson( model.Filters.and(model.Filters.and(model.Filters.eq("a", 1), model.Filters.eq("b", 2)), model.Filters.eq("c", 3)) - ) should equal(Document("""{$and: [{$and: [{a : 1}, {b : 2}]}, {c : 3}]}""")) + ) should equal(Document("""{$and: [{$and: [{a : {$eq: 1}}, {b : {$eq: 2}}]}, {c : {$eq: 3}}]}""")) toBson( model.Filters.and(model.Filters.and(model.Filters.eq("a", 1), model.Filters.eq("a", 2)), model.Filters.eq("c", 3)) - ) should equal(Document("""{$and: [{$and:[{a : 1}, {a : 2}]}, {c : 3}] }""")) + ) should equal(Document("""{$and: [{$and:[{a : {$eq: 1}}, {a : {$eq: 2}}]}, {c : {$eq: 3}}] }""")) toBson(model.Filters.and(model.Filters.lt("a", 1), model.Filters.lt("b", 2))) should equal( Document("""{$and: [{a : {$lt : 1}}, {b : {$lt : 2} }]}""") ) @@ -163,7 +163,7 @@ class FiltersSpec extends BaseSpec { toBson( model.Filters .elemMatch("results", model.Filters.and(model.Filters.eq("product", "xyz"), model.Filters.gt("score", 8))) - ) should equal(Document("""{ results : {$elemMatch : {$and: [{product : "xyz"}, {score : {$gt : 8}}]}}}""")) + ) should equal(Document("""{ results : {$elemMatch : {$and: [{product : {$eq: "xyz"}}, {score : {$gt : 8}}]}}}""")) } it should "render $in" in { From 926b519b29c5fe5d0b04de7e80312cee2e6a6520 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 22 Sep 2023 11:12:23 -0600 Subject: [PATCH 2/7] Add API docs JAVA-5117 --- .../com/mongodb/client/model/Aggregates.java | 44 ++++++++++++------- .../com/mongodb/client/model/Projections.java | 6 ++- .../model/search/VectorSearchOptions.java | 8 +++- .../org/mongodb/scala/model/Aggregates.scala | 40 +++++++++-------- .../org/mongodb/scala/model/Projections.scala | 14 ++++++ .../model/search/VectorSearchOptions.scala | 2 +- .../mongodb/scala/model/search/package.scala | 2 +- 7 files changed, 76 insertions(+), 40 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index e6ea14a87a2..dfdf5e14cf0 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -939,14 +939,19 @@ public static Bson searchMeta(final SearchCollector collector, final SearchOptio } /** - * VAKOTODO - * @param path - * @param queryVector - * @param index - * @param numCandidates - * @param limit - * @return - * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * Creates a {@code $vectorSearch} pipeline stage supported by MongoDB Atlas. + * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, + * to extract the relevance score assigned to each found document. + * + * @param queryVector The query vector. + * @param path The field to be searched. + * @param index The name of the index to use. This index predetermines the number of components of {@code queryVector}. + * @param numCandidates The number of candidates. + * @param limit The limit on the number of documents produced by the pipeline stage. + * @return The {@code $vectorSearch} pipeline stage. + * + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch + * @mongodb.atlas.manual atlas-search/scoring/ Scoring * @mongodb.server.release 7.1 * @since 4.11 */ @@ -962,15 +967,20 @@ public static Bson vectorSearch( } /** - * VAKOTODO - * @param queryVector - * @param path - * @param index - * @param numCandidates - * @param limit - * @param options - * @return - * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * Creates a {@code $vectorSearch} pipeline stage supported by MongoDB Atlas. + * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, + * to extract the relevance score assigned to each found document. + * + * @param queryVector The query vector. + * @param path The field to be searched. + * @param index The name of the index to use. This index predetermines the number of components of {@code queryVector}. + * @param numCandidates The number of candidates. + * @param limit The limit on the number of documents produced by the pipeline stage. + * @param options Optional {@code $vectorSearch} pipeline stage fields. + * @return The {@code $vectorSearch} pipeline stage. + * + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch + * @mongodb.atlas.manual atlas-search/scoring/ Scoring * @mongodb.server.release 7.1 * @since 4.11 */ diff --git a/driver-core/src/main/com/mongodb/client/model/Projections.java b/driver-core/src/main/com/mongodb/client/model/Projections.java index 55f0bb69cd6..682892a777a 100644 --- a/driver-core/src/main/com/mongodb/client/model/Projections.java +++ b/driver-core/src/main/com/mongodb/client/model/Projections.java @@ -16,6 +16,7 @@ package com.mongodb.client.model; +import com.mongodb.annotations.Beta; import com.mongodb.client.model.search.FieldSearchPath; import com.mongodb.client.model.search.SearchCollector; import com.mongodb.client.model.search.SearchCount; @@ -165,6 +166,7 @@ public static Bson elemMatch(final String fieldName, final Bson filter) { * @since 4.1 * @see #metaTextScore(String) * @see #metaSearchScore(String) + * @see #metaVectorSearchScore(String) * @see #metaSearchHighlights(String) */ public static Bson meta(final String fieldName, final String metaFieldName) { @@ -206,8 +208,10 @@ public static Bson metaSearchScore(final String fieldName) { * @param fieldName the field name * @return the projection * @mongodb.atlas.manual atlas-search/scoring/ Scoring - * @since 4.7 + * @mongodb.server.release 7.1 + * @since 4.11 */ + @Beta(Beta.Reason.SERVER) public static Bson metaVectorSearchScore(final String fieldName) { return meta(fieldName, "vectorSearchScore"); } diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java index 15fef7716ce..c1c7bc7480a 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java @@ -18,13 +18,14 @@ import com.mongodb.annotations.Beta; import com.mongodb.annotations.Sealed; import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.Filters; import org.bson.conversions.Bson; /** * Represents optional fields of the {@code $vectorSearch} pipeline stage of an aggregation pipeline. * * @see Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions) - * @mongodb.atlas.manual / @mongodb.driver.dochub VAKOTODO + * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch * @mongodb.server.release 7.1 * @since 4.11 */ @@ -34,7 +35,10 @@ public interface VectorSearchOptions extends Bson { /** * Creates a new {@link VectorSearchOptions} with the filter specified. * - * @param filter VAKOTODO + * @param filter A filter that is applied before applying the + * {@link Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions) queryVector}. + * One may use {@link Filters} to create this filter, though not all filters may be supported. + * See the MongoDB documentation for the list of supported filters. * @return A new {@link VectorSearchOptions}. */ VectorSearchOptions filter(Bson filter); diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala index 99248240645..66e25954040 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala @@ -723,15 +723,17 @@ object Aggregates { JAggregates.searchMeta(collector, options) /** - * VAKOTODO - * - * @param queryVector - * @param path - * @param index - * @param numCandidates - * @param limit - * @return - * @see [[VAKOTODO \$vectorSearch]] + * Creates a `\$vectorSearch` pipeline stage supported by MongoDB Atlas. + * You may use the `\$meta: "vectorSearchScore"` expression, e.g., via [[Projections.metaVectorSearchScore]], + * to extract the relevance score assigned to each found document. + * + * @param queryVector The query vector. + * @param path The field to be searched. + * @param index The name of the index to use. This index predetermines the number of components of `queryVector`. + * @param numCandidates The number of candidates. + * @param limit The limit on the number of documents produced by the pipeline stage. + * @return The `\$vectorSearch` pipeline stage. + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] * @note Requires MongoDB 7.1 or greater * @since 4.11 */ @@ -746,16 +748,18 @@ object Aggregates { JAggregates.vectorSearch(path, queryVector.asJava, index, numCandidates, limit) /** - * VAKOTODO + * Creates a `\$vectorSearch` pipeline stage supported by MongoDB Atlas. + * You may use the `\$meta: "vectorSearchScore"` expression, e.g., via [[Projections.metaVectorSearchScore]], + * to extract the relevance score assigned to each found document. * - * @param queryVector - * @param path - * @param index - * @param numCandidates - * @param limit - * @param options - * @return - * @see [[VAKOTODO \$vectorSearch]] + * @param queryVector The query vector. + * @param path The field to be searched. + * @param index The name of the index to use. This index predetermines the number of components of `queryVector`. + * @param numCandidates The number of candidates. + * @param limit The limit on the number of documents produced by the pipeline stage. + * @param options Optional `\$vectorSearch` pipeline stage fields. + * @return The `\$vectorSearch` pipeline stage. + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] * @note Requires MongoDB 7.1 or greater * @since 4.11 */ diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala index 36de461cbda..7ab24e501ad 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala @@ -112,6 +112,7 @@ object Projections { * @see [[https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/ meta]] * @see [[Projections.metaTextScore]] * @see [[Projections.metaSearchScore]] + * @see [[Projections.metaVectorSearchScore]] * @see [[Projections.metaSearchHighlights]] * @since 4.1 */ @@ -139,6 +140,19 @@ object Projections { */ def metaSearchScore(fieldName: String): Bson = JProjections.metaSearchScore(fieldName) + /** + * Creates a projection to the given field name of the vectorSearchScore, + * for use with `Aggregates.vectorSearch(FieldSearchPath, Iterable, String, Long, Long, VectorSearchOptions)`. + * Calling this method is equivalent to calling [[Projections.meta]] with `"vectorSearchScore"` as the second argument. + * + * @param fieldName the field name + * @return the projection + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/scoring/ Scoring]] + * @note Requires MongoDB 7.1 or greater + * @since 4.11 + */ + def metaVectorSearchScore(fieldName: String): Bson = JProjections.metaVectorSearchScore(fieldName) + /** * Creates a projection to the given field name of the searchHighlights, * for use with `Aggregates.search(SearchOperator, SearchOptions)` / `Aggregates.search(SearchCollector, SearchOptions)`. diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala index 109bb8e42a6..37661b1c8e7 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala @@ -21,7 +21,7 @@ import com.mongodb.client.model.search.{ VectorSearchOptions => JVectorSearchOpt /** * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * - * @see [[VAKOTODO \$vectorSearch syntax]] + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] * @note Requires MongoDB 7.1 or greater * @since 4.11 */ diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala index acd8a226d89..4bcdfd10194 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala @@ -222,7 +222,7 @@ package object search { /** * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * - * @see [[VAKOTODO \$vectorSearch syntax]] + * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] */ @Sealed @Beta(Array(Beta.Reason.SERVER)) From d9b071ba279539b8c7b2359cab8e66bccaa97bfd Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 22 Sep 2023 11:34:06 -0600 Subject: [PATCH 3/7] Roll back `Filters.eq` changes JAVA-5117 --- .../com/mongodb/client/model/Filters.java | 8 ++- .../model/AggregatesSpecification.groovy | 10 ++-- .../client/model/FiltersSpecification.groovy | 58 +++++++++---------- .../model/ProjectionsSpecification.groovy | 5 +- .../org/mongodb/scala/model/Filters.scala | 6 +- .../mongodb/scala/model/AggregatesSpec.scala | 10 ++-- .../org/mongodb/scala/model/FiltersSpec.scala | 34 +++++------ 7 files changed, 63 insertions(+), 68 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/Filters.java b/driver-core/src/main/com/mongodb/client/model/Filters.java index a9fc527263e..c516fe28930 100644 --- a/driver-core/src/main/com/mongodb/client/model/Filters.java +++ b/driver-core/src/main/com/mongodb/client/model/Filters.java @@ -61,7 +61,8 @@ private Filters() { } /** - * Creates a filter that matches all documents where the value of _id field equals the specified value. + * Creates a filter that matches all documents where the value of _id field equals the specified value. Note that this doesn't + * actually generate a $eq operator, as the query language doesn't require it. * * @param value the value, which may be null * @param the value type @@ -75,7 +76,8 @@ public static Bson eq(@Nullable final TItem value) { } /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. + * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this doesn't + * actually generate a $eq operator, as the query language doesn't require it. * * @param fieldName the field name * @param value the value, which may be null @@ -84,7 +86,7 @@ public static Bson eq(@Nullable final TItem value) { * @mongodb.driver.manual reference/operator/query/eq $eq */ public static Bson eq(final String fieldName, @Nullable final TItem value) { - return new OperatorFilter<>("$eq", fieldName, value); + return new SimpleEncodingFilter<>(fieldName, value); } /** diff --git a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy index 9bcdb80b491..aee7fea8fa1 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy @@ -243,7 +243,7 @@ class AggregatesSpecification extends Specification { def 'should render $match'() { expect: - toBson(match(eq('author', 'dave'))) == parse('{ $match : { author : { $eq: "dave" } } }') + toBson(match(eq('author', 'dave'))) == parse('{ $match : { author : "dave" } }') } def 'should render $project'() { @@ -316,7 +316,7 @@ class AggregatesSpecification extends Specification { parse('''{$facet: { "Screen Sizes": [ {$unwind: "$attributes"}, - {$match: {"attributes.name": {$eq: "screen size"}}}, + {$match: {"attributes.name": "screen size"}}, {$group: { _id: null, count: {$sum: 1} @@ -324,7 +324,7 @@ class AggregatesSpecification extends Specification { ], "Manufacturer": [ - {$match: {"attributes.name": {$eq: "manufacturer"}}}, + {$match: {"attributes.name": "manufacturer"}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, {$sort: {count: -1}} {$limit: 5} @@ -353,7 +353,7 @@ class AggregatesSpecification extends Specification { toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() .restrictSearchWithMatch(eq('hobbies', 'golf')))) == parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name", - as: "socialNetwork", restrictSearchWithMatch : { "hobbies" : { $eq: "golf" } } } }''') + as: "socialNetwork", restrictSearchWithMatch : { "hobbies" : "golf" } } }''') // with maxDepth and depthField toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() @@ -365,7 +365,7 @@ class AggregatesSpecification extends Specification { toBson(graphLookup('contacts', '$friends', 'friends', 'name', 'socialNetwork', new GraphLookupOptions() .maxDepth(1).depthField('depth').restrictSearchWithMatch(eq('hobbies', 'golf')))) == parse('''{ $graphLookup: { from: "contacts", startWith: "$friends", connectFromField: "friends", connectToField: "name", - as: "socialNetwork", maxDepth: 1, depthField: "depth", restrictSearchWithMatch : { "hobbies" : { $eq: "golf" } } } }''') + as: "socialNetwork", maxDepth: 1, depthField: "depth", restrictSearchWithMatch : { "hobbies" : "golf" } } }''') } def 'should render $skip'() { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy index 1e0a09ae085..9c9a4bc8748 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy @@ -72,9 +72,9 @@ class FiltersSpecification extends Specification { def 'eq should render without $eq'() { expect: - toBson(eq('x', 1)) == parse('{x : {$eq: 1}}') - toBson(eq('x', null)) == parse('{x : {$eq: null}}') - toBson(eq(1)) == parse('{_id : {$eq: 1}}') + toBson(eq('x', 1)) == parse('{x : 1}') + toBson(eq('x', null)) == parse('{x : null}') + toBson(eq(1)) == parse('{_id : 1}') } def 'should render $ne'() { @@ -89,13 +89,13 @@ class FiltersSpecification extends Specification { toBson(not(gt('x', 1))) == parse('{x : {$not: {$gt: 1}}}') toBson(not(regex('x', '^p.*'))) == parse('{x : {$not: /^p.*/}}') - toBson(not(and(gt('x', 1), eq('y', 20)))) == parse('{$not: {$and: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}') - toBson(not(and(eq('x', 1), eq('x', 2)))) == parse('{$not: {$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}') - toBson(not(and(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$and: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}') + toBson(not(and(gt('x', 1), eq('y', 20)))) == parse('{$not: {$and: [{x: {$gt: 1}}, {y: 20}]}}') + toBson(not(and(eq('x', 1), eq('x', 2)))) == parse('{$not: {$and: [{x: 1}, {x: 2}]}}') + toBson(not(and(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$and: [{x: {$in: [1, 2]}}, {x: 3}]}}') - toBson(not(or(gt('x', 1), eq('y', 20)))) == parse('{$not: {$or: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}') - toBson(not(or(eq('x', 1), eq('x', 2)))) == parse('{$not: {$or: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}') - toBson(not(or(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$or: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}') + toBson(not(or(gt('x', 1), eq('y', 20)))) == parse('{$not: {$or: [{x: {$gt: 1}}, {y: 20}]}}') + toBson(not(or(eq('x', 1), eq('x', 2)))) == parse('{$not: {$or: [{x: 1}, {x: 2}]}}') + toBson(not(or(Filters.in('x', 1, 2), eq('x', 3)))) == parse('{$not: {$or: [{x: {$in: [1, 2]}}, {x: 3}]}}') toBson(not(parse('{$in: [1]}'))) == parse('{$not: {$in: [1]}}') @@ -106,8 +106,8 @@ class FiltersSpecification extends Specification { def 'should render $nor'() { expect: - toBson(nor(eq('price', 1))) == parse('{$nor : [{price: {$eq: 1}}]}') - toBson(nor(eq('price', 1), eq('sale', true))) == parse('{$nor : [{price: {$eq: 1}}, {sale: {$eq: true}}]}') + toBson(nor(eq('price', 1))) == parse('{$nor : [{price: 1}]}') + toBson(nor(eq('price', 1), eq('sale', true))) == parse('{$nor : [{price: 1}, {sale: true}]}') } def 'should render $gt'() { @@ -144,8 +144,8 @@ class FiltersSpecification extends Specification { def 'should render $or'() { expect: - toBson(or([eq('x', 1), eq('y', 2)])) == parse('{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}') - toBson(or(eq('x', 1), eq('y', 2))) == parse('{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}') + toBson(or([eq('x', 1), eq('y', 2)])) == parse('{$or : [{x : 1}, {y : 2}]}') + toBson(or(eq('x', 1), eq('y', 2))) == parse('{$or : [{x : 1}, {y : 2}]}') } def 'and should render empty and using $and'() { @@ -156,21 +156,21 @@ class FiltersSpecification extends Specification { def 'and should render using $and'() { expect: - toBson(and([eq('x', 1), eq('y', 2)])) == parse('{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}') - toBson(and(eq('x', 1), eq('y', 2))) == parse('{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}') + toBson(and([eq('x', 1), eq('y', 2)])) == parse('{$and: [{x : 1}, {y : 2}]}') + toBson(and(eq('x', 1), eq('y', 2))) == parse('{$and: [{x : 1}, {y : 2}]}') } def 'and should render $and with clashing keys'() { expect: - toBson(and([eq('a', 1), eq('a', 2)])) == parse('{$and: [{a: {$eq: 1}}, {a: {$eq: 2}}]}') + toBson(and([eq('a', 1), eq('a', 2)])) == parse('{$and: [{a: 1}, {a: 2}]}') } def 'and should not flatten nested'() { expect: toBson(and([and([eq('a', 1), eq('b', 2)]), eq('c', 3)])) == - parse('{$and: [{$and: [{a : {$eq: 1}}, {b : {$eq: 2}}]}, {c : {$eq: 3}}]}') + parse('{$and: [{$and: [{a : 1}, {b : 2}]}, {c : 3}]}') toBson(and([and([eq('a', 1), eq('a', 2)]), eq('c', 3)])) == - parse('{$and:[{$and: [{a : {$eq: 1}}, {a : {$eq: 2}}]}, {c : {$eq: 3}}]} }') + parse('{$and:[{$and: [{a : 1}, {a : 2}]}, {c : 3}]} }') toBson(and([lt('a', 1), lt('b', 2)])) == parse('{$and: [{a : {$lt : 1}}, {b : {$lt : 2}}]}') toBson(and([lt('a', 1), lt('a', 2)])) == @@ -224,7 +224,7 @@ class FiltersSpecification extends Specification { parse('{results : {$elemMatch : {$gte: 80, $lt: 85}}}') toBson(elemMatch('results', and(eq('product', 'xyz'), gt('score', 8)))) == - parse('{ results : {$elemMatch : {$and: [{product : {$eq: "xyz"}}, {score : {$gt : 8}}]}}}') + parse('{ results : {$elemMatch : {$and: [{product : "xyz"}, {score : {$gt : 8}}]}}}') } def 'should render $in'() { @@ -668,17 +668,17 @@ class FiltersSpecification extends Specification { def 'should render with iterable value'() { expect: toBson(eq('x', new Document())) == parse('''{ - x : {$eq: {}} + x : {} }''') toBson(eq('x', [1, 2, 3])) == parse('''{ - x : {$eq: [1, 2, 3]} + x : [1, 2, 3] }''') } def 'should create string representation for simple filter'() { expect: - eq('x', 1).toString() == 'Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + eq('x', 1).toString() == 'Filter{fieldName=\'x\', value=1}' } def 'should create string representation for regex filter'() { @@ -694,16 +694,10 @@ class FiltersSpecification extends Specification { def 'should create string representation for compound filters'() { expect: - and(eq('x', 1), eq('y', 2)).toString() == - 'And Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + - ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' - or(eq('x', 1), eq('y', 2)).toString() == - 'Or Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + - ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' - nor(eq('x', 1), eq('y', 2)).toString() == - 'Nor Filter{filters=[Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}' + - ', Operator Filter{fieldName=\'y\', operator=\'$eq\', value=2}]}' - not(eq('x', 1)).toString() == 'Not Filter{filter=Operator Filter{fieldName=\'x\', operator=\'$eq\', value=1}}' + and(eq('x', 1), eq('y', 2)).toString() == 'And Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' + or(eq('x', 1), eq('y', 2)).toString() == 'Or Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' + nor(eq('x', 1), eq('y', 2)).toString() == 'Nor Filter{filters=[Filter{fieldName=\'x\', value=1}, Filter{fieldName=\'y\', value=2}]}' + not(eq('x', 1)).toString() == 'Not Filter{filter=Filter{fieldName=\'x\', value=1}}' } def 'should create string representation for geo filters'() { diff --git a/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy index fea79ab2ceb..6e7d0336037 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/ProjectionsSpecification.groovy @@ -63,7 +63,7 @@ class ProjectionsSpecification extends Specification { def 'elemMatch'() { expect: toBson(elemMatch('x', and(eq('y', 1), eq('z', 2)))) == - parse('{x : {$elemMatch : {$and: [{y : {$eq: 1}}, {z : {$eq: 2}}]}}}') + parse('{x : {$elemMatch : {$and: [{y : 1}, {z : 2}]}}}') } def 'slice'() { @@ -111,8 +111,7 @@ class ProjectionsSpecification extends Specification { expect: elemMatch('x', and(eq('y', 1), eq('z', 2))).toString() == 'ElemMatch Projection{fieldName=\'x\', ' + - 'filter=And Filter{filters=[Operator Filter{fieldName=\'y\', operator=\'$eq\', value=1}' + - ', Operator Filter{fieldName=\'z\', operator=\'$eq\', value=2}]}}' + 'filter=And Filter{filters=[Filter{fieldName=\'y\', value=1}, Filter{fieldName=\'z\', value=2}]}}' } def 'should create string representation for fields'() { diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala index 8d464d5bc91..cff938d6842 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala @@ -38,7 +38,8 @@ import org.mongodb.scala.bson.conversions.Bson object Filters { /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. + * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this doesn't + * actually generate a `\$eq` operator, as the query language doesn't require it. * * @param fieldName the field name * @param value the value @@ -60,7 +61,8 @@ object Filters { def expr[TExpression](expression: TExpression): Bson = JFilters.expr(expression) /** - * Creates a filter that matches all documents where the value of the field name equals the specified value. + * Creates a filter that matches all documents where the value of the field name equals the specified value. Note that this does + * actually generate a `\$eq` operator, as the query language doesn't require it. * * A friendly alias for the `eq` method. * diff --git a/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala index 390a2631343..18a24b085bf 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/model/AggregatesSpec.scala @@ -120,10 +120,8 @@ class AggregatesSpec extends BaseSpec { } it should "render $match" in { - toBson(`match`(Filters.eq("author", "dave"))) should equal( - Document("""{ $match : { author : { $eq: "dave" } } }""") - ) - toBson(filter(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : { $eq: "dave" } } }""")) + toBson(`match`(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : "dave" } }""")) + toBson(filter(Filters.eq("author", "dave"))) should equal(Document("""{ $match : { author : "dave" } }""")) } it should "render $facet" in { @@ -145,9 +143,9 @@ class AggregatesSpec extends BaseSpec { ) ) should equal( Document( - """{$facet: { "Screen Sizes": [{$unwind: "$attributes"}, {$match: {"attributes.name": {$eq: "screen size"}}}, + """{$facet: { "Screen Sizes": [{$unwind: "$attributes"}, {$match: {"attributes.name": "screen size"}}, {$group: { _id: null, count: {$sum: 1} }}], - "Manufacturer": [ {$match: {"attributes.name": {$eq: "manufacturer"}}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, + "Manufacturer": [ {$match: {"attributes.name": "manufacturer"}}, {$group: {_id: "$attributes.value", count: {$sum: 1}}}, {$sort: {count: -1}}, {$limit: 5}]}}""" ) ) diff --git a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala index a2d4b2a4935..52a7b4254c1 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala @@ -46,11 +46,11 @@ class FiltersSpec extends BaseSpec { } it should "render without $eq" in { - toBson(model.Filters.eq("x", 1)) should equal(Document("""{x : {$eq: 1}}""")) - toBson(model.Filters.eq("x", null)) should equal(Document("""{x : {$eq: null}}""")) + toBson(model.Filters.eq("x", 1)) should equal(Document("""{x : 1}""")) + toBson(model.Filters.eq("x", null)) should equal(Document("""{x : null}""")) - toBson(model.Filters.equal("x", 1)) should equal(Document("""{x : {$eq: 1}}""")) - toBson(model.Filters.equal("x", null)) should equal(Document("""{x : {$eq: null}}""")) + toBson(model.Filters.equal("x", 1)) should equal(Document("""{x : 1}""")) + toBson(model.Filters.equal("x", null)) should equal(Document("""{x : null}""")) } it should "render $ne" in { @@ -63,30 +63,30 @@ class FiltersSpec extends BaseSpec { toBson(model.Filters.not(model.Filters.gt("x", 1))) should equal(Document("""{x : {$not: {$gt: 1}}}""")) toBson(model.Filters.not(model.Filters.regex("x", "^p.*"))) should equal(Document("""{x : {$not: /^p.*/}}""")) toBson(model.Filters.not(model.Filters.and(model.Filters.gt("x", 1), model.Filters.eq("y", 20)))) should equal( - Document("""{$not: {$and: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}""") + Document("""{$not: {$and: [{x: {$gt: 1}}, {y: 20}]}}""") ) toBson(model.Filters.not(model.Filters.and(model.Filters.eq("x", 1), model.Filters.eq("x", 2)))) should equal( - Document("""{$not: {$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}""") + Document("""{$not: {$and: [{x: 1}, {x: 2}]}}""") ) toBson(model.Filters.not(model.Filters.and(model.Filters.in("x", 1, 2), model.Filters.eq("x", 3)))) should equal( - Document("""{$not: {$and: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}""") + Document("""{$not: {$and: [{x: {$in: [1, 2]}}, {x: 3}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.gt("x", 1), model.Filters.eq("y", 20)))) should equal( - Document("""{$not: {$or: [{x: {$gt: 1}}, {y: {$eq: 20}}]}}""") + Document("""{$not: {$or: [{x: {$gt: 1}}, {y: 20}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.eq("x", 1), model.Filters.eq("x", 2)))) should equal( - Document("""{$not: {$or: [{x: {$eq: 1}}, {x: {$eq: 2}}]}}""") + Document("""{$not: {$or: [{x: 1}, {x: 2}]}}""") ) toBson(model.Filters.not(model.Filters.or(model.Filters.in("x", 1, 2), model.Filters.eq("x", 3)))) should equal( - Document("""{$not: {$or: [{x: {$in: [1, 2]}}, {x: {$eq: 3}}]}}""") + Document("""{$not: {$or: [{x: {$in: [1, 2]}}, {x: 3}]}}""") ) toBson(model.Filters.not(Document("$in" -> List(1)))) should equal(Document("""{$not: {$in: [1]}}""")) } it should "render $nor" in { - toBson(model.Filters.nor(model.Filters.eq("price", 1))) should equal(Document("""{$nor : [{price: {$eq: 1}}]}""")) + toBson(model.Filters.nor(model.Filters.eq("price", 1))) should equal(Document("""{$nor : [{price: 1}]}""")) toBson(model.Filters.nor(model.Filters.eq("price", 1), model.Filters.eq("sale", true))) should equal( - Document("""{$nor : [{price: {$eq: 1}}, {sale: {$eq: true}}]}""") + Document("""{$nor : [{price: 1}, {sale: true}]}""") ) } @@ -117,7 +117,7 @@ class FiltersSpec extends BaseSpec { it should "render $or" in { toBson(model.Filters.or(model.Filters.eq("x", 1), model.Filters.eq("y", 2))) should equal( - Document("""{$or : [{x : {$eq: 1}}, {y : {$eq: 2}}]}""") + Document("""{$or : [{x : 1}, {y : 2}]}""") ) } @@ -127,7 +127,7 @@ class FiltersSpec extends BaseSpec { it should "and should render using $and" in { toBson(model.Filters.and(model.Filters.eq("x", 1), model.Filters.eq("y", 2))) should equal( - Document("""{$and: [{x : {$eq: 1}}, {y : {$eq: 2}}]}""") + Document("""{$and: [{x : 1}, {y : 2}]}""") ) } @@ -140,10 +140,10 @@ class FiltersSpec extends BaseSpec { it should "and should flatten nested" in { toBson( model.Filters.and(model.Filters.and(model.Filters.eq("a", 1), model.Filters.eq("b", 2)), model.Filters.eq("c", 3)) - ) should equal(Document("""{$and: [{$and: [{a : {$eq: 1}}, {b : {$eq: 2}}]}, {c : {$eq: 3}}]}""")) + ) should equal(Document("""{$and: [{$and: [{a : 1}, {b : 2}]}, {c : 3}]}""")) toBson( model.Filters.and(model.Filters.and(model.Filters.eq("a", 1), model.Filters.eq("a", 2)), model.Filters.eq("c", 3)) - ) should equal(Document("""{$and: [{$and:[{a : {$eq: 1}}, {a : {$eq: 2}}]}, {c : {$eq: 3}}] }""")) + ) should equal(Document("""{$and: [{$and:[{a : 1}, {a : 2}]}, {c : 3}] }""")) toBson(model.Filters.and(model.Filters.lt("a", 1), model.Filters.lt("b", 2))) should equal( Document("""{$and: [{a : {$lt : 1}}, {b : {$lt : 2} }]}""") ) @@ -163,7 +163,7 @@ class FiltersSpec extends BaseSpec { toBson( model.Filters .elemMatch("results", model.Filters.and(model.Filters.eq("product", "xyz"), model.Filters.gt("score", 8))) - ) should equal(Document("""{ results : {$elemMatch : {$and: [{product : {$eq: "xyz"}}, {score : {$gt : 8}}]}}}""")) + ) should equal(Document("""{ results : {$elemMatch : {$and: [{product : "xyz"}, {score : {$gt : 8}}]}}}""")) } it should "render $in" in { From 66e3364c5e0b33b4e607f6cad24e8622c858a079 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 22 Sep 2023 12:29:08 -0600 Subject: [PATCH 4/7] Add `Filters.eqFull` JAVA-5117 --- .../com/mongodb/client/model/Aggregates.java | 2 +- .../main/com/mongodb/client/model/Filters.java | 16 ++++++++++++++++ .../client/model/search/VectorSearchOptions.java | 2 ++ .../model/FiltersFunctionalSpecification.groovy | 1 + .../search/AggregatesSearchIntegrationTest.java | 6 +++--- .../client/model/FiltersSpecification.groovy | 7 +++++++ .../scala/org/mongodb/scala/model/Filters.scala | 13 +++++++++++++ .../org/mongodb/scala/model/search/package.scala | 2 ++ .../org/mongodb/scala/model/FiltersSpec.scala | 5 +++++ 9 files changed, 50 insertions(+), 4 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index dfdf5e14cf0..37654ff8484 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -1011,8 +1011,8 @@ public BsonDocument toBsonDocument(final Class documentCl @Override public String toString() { return "Stage{name=$vectorSearch" - + ", field=" + queryVector + ", path=" + path + + ", queryVector=" + queryVector + ", index=" + index + ", numCandidates=" + numCandidates + ", limit=" + limit diff --git a/driver-core/src/main/com/mongodb/client/model/Filters.java b/driver-core/src/main/com/mongodb/client/model/Filters.java index c516fe28930..5bf794ae2ae 100644 --- a/driver-core/src/main/com/mongodb/client/model/Filters.java +++ b/driver-core/src/main/com/mongodb/client/model/Filters.java @@ -84,11 +84,27 @@ public static Bson eq(@Nullable final TItem value) { * @param the value type * @return the filter * @mongodb.driver.manual reference/operator/query/eq $eq + * @see #eqFull(String, Object) */ public static Bson eq(final String fieldName, @Nullable final TItem value) { return new SimpleEncodingFilter<>(fieldName, value); } + /** + * Creates a filter that matches all documents where the value of the field name equals the specified value. + * Unlike {@link #eq(String, Object)}, this method creates a full form of {@code $eq}. + * + * @param fieldName the field name + * @param value the value, which may be null + * @param the value type + * @return the filter + * @mongodb.driver.manual reference/operator/query/eq $eq + * @since 4.11 + */ + public static Bson eqFull(final String fieldName, @Nullable final TItem value) { + return new OperatorFilter<>("$eq", fieldName, value); + } + /** * Creates a filter that matches all documents where the value of the field name does not equal the specified value. * diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java index c1c7bc7480a..4f09cf0699b 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java @@ -39,6 +39,8 @@ public interface VectorSearchOptions extends Bson { * {@link Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions) queryVector}. * One may use {@link Filters} to create this filter, though not all filters may be supported. * See the MongoDB documentation for the list of supported filters. + *

+ * Note that for now one has to use {@link Filters#eqFull(String, Object)} instead of {@link Filters#eq(String, Object)}.

* @return A new {@link VectorSearchOptions}. */ VectorSearchOptions filter(Bson filter); diff --git a/driver-core/src/test/functional/com/mongodb/client/model/FiltersFunctionalSpecification.groovy b/driver-core/src/test/functional/com/mongodb/client/model/FiltersFunctionalSpecification.groovy index 3febde2f243..777bcb7545c 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/FiltersFunctionalSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/client/model/FiltersFunctionalSpecification.groovy @@ -80,6 +80,7 @@ class FiltersFunctionalSpecification extends OperationFunctionalSpecification { def 'eq'() { expect: find(eq('x', 1)) == [a] + find(eq('_id', 2)) == [b] find(eq(2)) == [b] } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java index f7d9437a624..2c5cfc2b9df 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java @@ -56,7 +56,7 @@ import static com.mongodb.client.model.Aggregates.project; import static com.mongodb.client.model.Aggregates.replaceWith; import static com.mongodb.client.model.Filters.and; -import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.eqFull; import static com.mongodb.client.model.Filters.gt; import static com.mongodb.client.model.Filters.gte; import static com.mongodb.client.model.Filters.in; @@ -291,14 +291,14 @@ void vectorSearchSupportedFilters() { assertAll( () -> asserter.accept(lt("year", 2016)), () -> asserter.accept(lte("year", 2016)), - () -> asserter.accept(eq("year", 2016)), + () -> asserter.accept(eqFull("year", 2016)), () -> asserter.accept(gte("year", 2016)), () -> asserter.accept(gt("year", 2015)), () -> asserter.accept(ne("year", 2016)), () -> asserter.accept(in("year", 2000, 2016)), () -> asserter.accept(nin("year", 2000, 2016)), () -> asserter.accept(and(gte("year", 2015), lte("year", 2016))), - () -> asserter.accept(or(eq("year", 2015), eq("year", 2016))) + () -> asserter.accept(or(eqFull("year", 2015), eqFull("year", 2016))) ); } diff --git a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy index 9c9a4bc8748..478752a8584 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/FiltersSpecification.groovy @@ -42,6 +42,7 @@ import static com.mongodb.client.model.Filters.bitsAnySet import static com.mongodb.client.model.Filters.elemMatch import static com.mongodb.client.model.Filters.empty import static com.mongodb.client.model.Filters.eq +import static com.mongodb.client.model.Filters.eqFull import static com.mongodb.client.model.Filters.expr import static com.mongodb.client.model.Filters.geoIntersects import static com.mongodb.client.model.Filters.geoWithin @@ -77,6 +78,12 @@ class FiltersSpecification extends Specification { toBson(eq(1)) == parse('{_id : 1}') } + def 'should render eqFull'() { + expect: + toBson(eqFull('x', 1)) == parse('{x : {$eq: 1}}') + toBson(eqFull('x', null)) == parse('{x : {$eq: null}}') + } + def 'should render $ne'() { expect: toBson(ne('x', 1)) == parse('{x : {$ne : 1} }') diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala index cff938d6842..d4e49a18182 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala @@ -49,6 +49,19 @@ object Filters { */ def eq[TItem](fieldName: String, value: TItem): Bson = JFilters.eq(fieldName, value) + /** + * Creates a filter that matches all documents where the value of the field name equals the specified value. + * Unlike `Filters.eq`, this method creates a full form of `\$eq`. + * + * @param fieldName the field name + * @param value the value + * @tparam TItem the value type + * @return the filter + * @see [[https://www.mongodb.com/docs/manual/reference/operator/query/eq \$eq]] + * @since 4.11 + */ + def eqFull[TItem](fieldName: String, value: TItem): Bson = JFilters.eqFull(fieldName, value) + /** * Allows the use of aggregation expressions within the query language. * diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala index 4bcdfd10194..5b7ac5030d2 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala @@ -223,6 +223,8 @@ package object search { * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] + * @note Requires MongoDB 7.1 or greater + * @since 4.11 */ @Sealed @Beta(Array(Beta.Reason.SERVER)) diff --git a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala index 52a7b4254c1..26e1ec0d127 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/model/FiltersSpec.scala @@ -53,6 +53,11 @@ class FiltersSpec extends BaseSpec { toBson(model.Filters.equal("x", null)) should equal(Document("""{x : null}""")) } + it should "render eqFull" in { + toBson(model.Filters.eqFull("x", 1)) should equal(Document("""{x : {$eq: 1}}""")) + toBson(model.Filters.eqFull("x", null)) should equal(Document("""{x : {$eq: null}}""")) + } + it should "render $ne" in { toBson(model.Filters.ne("x", 1)) should equal(Document("""{x : {$ne : 1} }""")) toBson(model.Filters.ne("x", null)) should equal(Document("""{x : {$ne : null} }""")) From 02d76f7b55e2ac80b337eea0f406fceadc6048dc Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 22 Sep 2023 13:06:22 -0600 Subject: [PATCH 5/7] Mark `Filters.eqFull` as `@Beta` JAVA-5117 --- driver-core/src/main/com/mongodb/client/model/Filters.java | 4 ++++ .../src/main/scala/org/mongodb/scala/model/Filters.scala | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/driver-core/src/main/com/mongodb/client/model/Filters.java b/driver-core/src/main/com/mongodb/client/model/Filters.java index 5bf794ae2ae..b247a62595b 100644 --- a/driver-core/src/main/com/mongodb/client/model/Filters.java +++ b/driver-core/src/main/com/mongodb/client/model/Filters.java @@ -16,6 +16,7 @@ package com.mongodb.client.model; +import com.mongodb.annotations.Beta; import com.mongodb.client.model.geojson.Geometry; import com.mongodb.client.model.geojson.Point; import com.mongodb.client.model.search.SearchCollector; @@ -93,6 +94,8 @@ public static Bson eq(final String fieldName, @Nullable final TItem valu /** * Creates a filter that matches all documents where the value of the field name equals the specified value. * Unlike {@link #eq(String, Object)}, this method creates a full form of {@code $eq}. + * This method exists temporarily until Atlas starts supporting the short form of {@code $eq}. + * It will likely be removed in the next driver release. * * @param fieldName the field name * @param value the value, which may be null @@ -101,6 +104,7 @@ public static Bson eq(final String fieldName, @Nullable final TItem valu * @mongodb.driver.manual reference/operator/query/eq $eq * @since 4.11 */ + @Beta(Beta.Reason.SERVER) public static Bson eqFull(final String fieldName, @Nullable final TItem value) { return new OperatorFilter<>("$eq", fieldName, value); } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala index d4e49a18182..a02d0110ca2 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Filters.scala @@ -16,6 +16,8 @@ package org.mongodb.scala.model +import com.mongodb.annotations.Beta + import java.lang import scala.collection.JavaConverters._ @@ -52,6 +54,8 @@ object Filters { /** * Creates a filter that matches all documents where the value of the field name equals the specified value. * Unlike `Filters.eq`, this method creates a full form of `\$eq`. + * This method exists temporarily until Atlas starts supporting the short form of `\$eq`. + * It will likely be removed in the next driver release. * * @param fieldName the field name * @param value the value @@ -60,6 +64,7 @@ object Filters { * @see [[https://www.mongodb.com/docs/manual/reference/operator/query/eq \$eq]] * @since 4.11 */ + @Beta(Array(Beta.Reason.SERVER)) def eqFull[TItem](fieldName: String, value: TItem): Bson = JFilters.eqFull(fieldName, value) /** From e67c262c78f852759c615c8f694f72f339382fa8 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Fri, 22 Sep 2023 15:32:52 -0600 Subject: [PATCH 6/7] Address review concerns JAVA-5117 --- .../main/com/mongodb/client/model/Aggregates.java | 8 ++++---- .../search/AggregatesSearchIntegrationTest.java | 3 ++- .../scala/org/mongodb/scala/model/Aggregates.scala | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index 37654ff8484..cfaba8e56ae 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -943,9 +943,9 @@ public static Bson searchMeta(final SearchCollector collector, final SearchOptio * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, * to extract the relevance score assigned to each found document. * - * @param queryVector The query vector. + * @param queryVector The query vector. The number of dimensions must match that of the {@code index}. * @param path The field to be searched. - * @param index The name of the index to use. This index predetermines the number of components of {@code queryVector}. + * @param index The name of the index to use. * @param numCandidates The number of candidates. * @param limit The limit on the number of documents produced by the pipeline stage. * @return The {@code $vectorSearch} pipeline stage. @@ -971,9 +971,9 @@ public static Bson vectorSearch( * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)}, * to extract the relevance score assigned to each found document. * - * @param queryVector The query vector. + * @param queryVector The query vector. The number of dimensions must match that of the {@code index}. * @param path The field to be searched. - * @param index The name of the index to use. This index predetermines the number of components of {@code queryVector}. + * @param index The name of the index to use. * @param numCandidates The number of candidates. * @param limit The limit on the number of documents produced by the pipeline stage. * @param options Optional {@code $vectorSearch} pipeline stage fields. diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java index 2c5cfc2b9df..18ab9259393 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesSearchIntegrationTest.java @@ -240,11 +240,11 @@ static void beforeAll() { @BeforeEach void beforeEach() { assumeTrue(isAtlasSearchTest()); - assumeTrue(serverVersionAtLeast(7, 1)); } @Test void vectorSearch() { + assumeTrue(serverVersionAtLeast(7, 1)); CollectionHelper collectionHelper = collectionHelpers.get(MFLIX_EMBEDDED_MOVIES_NS); int limit = 2; assertAll( @@ -278,6 +278,7 @@ void vectorSearch() { @Test void vectorSearchSupportedFilters() { + assumeTrue(serverVersionAtLeast(7, 1)); CollectionHelper collectionHelper = collectionHelpers.get(MFLIX_EMBEDDED_MOVIES_NS); Consumer asserter = filter -> { List pipeline = singletonList( diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala index 66e25954040..d9c0814ae97 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala @@ -727,11 +727,11 @@ object Aggregates { * You may use the `\$meta: "vectorSearchScore"` expression, e.g., via [[Projections.metaVectorSearchScore]], * to extract the relevance score assigned to each found document. * - * @param queryVector The query vector. - * @param path The field to be searched. - * @param index The name of the index to use. This index predetermines the number of components of `queryVector`. + * @param queryVector The query vector. The number of dimensions must match that of the `index`. + * @param path The field to be searched. + * @param index The name of the index to use. * @param numCandidates The number of candidates. - * @param limit The limit on the number of documents produced by the pipeline stage. + * @param limit The limit on the number of documents produced by the pipeline stage. * @return The `\$vectorSearch` pipeline stage. * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] * @note Requires MongoDB 7.1 or greater @@ -752,9 +752,9 @@ object Aggregates { * You may use the `\$meta: "vectorSearchScore"` expression, e.g., via [[Projections.metaVectorSearchScore]], * to extract the relevance score assigned to each found document. * - * @param queryVector The query vector. + * @param queryVector The query vector. The number of dimensions must match that of the `index`. * @param path The field to be searched. - * @param index The name of the index to use. This index predetermines the number of components of `queryVector`. + * @param index The name of the index to use. * @param numCandidates The number of candidates. * @param limit The limit on the number of documents produced by the pipeline stage. * @param options Optional `\$vectorSearch` pipeline stage fields. From 44909a10290805030681f97af3b07374e3b90895 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Mon, 25 Sep 2023 17:21:42 -0600 Subject: [PATCH 7/7] 7.1 -> 6.0.10 JAVA-5117 --- driver-core/src/main/com/mongodb/client/model/Aggregates.java | 4 ++-- .../src/main/com/mongodb/client/model/Projections.java | 2 +- .../com/mongodb/client/model/search/VectorSearchOptions.java | 2 +- .../src/main/scala/org/mongodb/scala/model/Aggregates.scala | 4 ++-- .../src/main/scala/org/mongodb/scala/model/Projections.scala | 2 +- .../org/mongodb/scala/model/search/VectorSearchOptions.scala | 2 +- .../main/scala/org/mongodb/scala/model/search/package.scala | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java index cfaba8e56ae..60980a6c481 100644 --- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java +++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java @@ -952,7 +952,7 @@ public static Bson searchMeta(final SearchCollector collector, final SearchOptio * * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch * @mongodb.atlas.manual atlas-search/scoring/ Scoring - * @mongodb.server.release 7.1 + * @mongodb.server.release 6.0.10 * @since 4.11 */ @Beta(Beta.Reason.SERVER) @@ -981,7 +981,7 @@ public static Bson vectorSearch( * * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch * @mongodb.atlas.manual atlas-search/scoring/ Scoring - * @mongodb.server.release 7.1 + * @mongodb.server.release 6.0.10 * @since 4.11 */ @Beta(Beta.Reason.SERVER) diff --git a/driver-core/src/main/com/mongodb/client/model/Projections.java b/driver-core/src/main/com/mongodb/client/model/Projections.java index 682892a777a..6ef7bc99729 100644 --- a/driver-core/src/main/com/mongodb/client/model/Projections.java +++ b/driver-core/src/main/com/mongodb/client/model/Projections.java @@ -208,7 +208,7 @@ public static Bson metaSearchScore(final String fieldName) { * @param fieldName the field name * @return the projection * @mongodb.atlas.manual atlas-search/scoring/ Scoring - * @mongodb.server.release 7.1 + * @mongodb.server.release 6.0.10 * @since 4.11 */ @Beta(Beta.Reason.SERVER) diff --git a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java index 4f09cf0699b..a17a41e8748 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java +++ b/driver-core/src/main/com/mongodb/client/model/search/VectorSearchOptions.java @@ -26,7 +26,7 @@ * * @see Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, long, VectorSearchOptions) * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch - * @mongodb.server.release 7.1 + * @mongodb.server.release 6.0.10 * @since 4.11 */ @Sealed diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala index d9c0814ae97..fc3196f76f6 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Aggregates.scala @@ -734,7 +734,7 @@ object Aggregates { * @param limit The limit on the number of documents produced by the pipeline stage. * @return The `\$vectorSearch` pipeline stage. * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] - * @note Requires MongoDB 7.1 or greater + * @note Requires MongoDB 6.0.10 or greater * @since 4.11 */ @Beta(Array(Beta.Reason.SERVER)) @@ -760,7 +760,7 @@ object Aggregates { * @param options Optional `\$vectorSearch` pipeline stage fields. * @return The `\$vectorSearch` pipeline stage. * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] - * @note Requires MongoDB 7.1 or greater + * @note Requires MongoDB 6.0.10 or greater * @since 4.11 */ @Beta(Array(Beta.Reason.SERVER)) diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala index 7ab24e501ad..989258aaa59 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/Projections.scala @@ -148,7 +148,7 @@ object Projections { * @param fieldName the field name * @return the projection * @see [[https://www.mongodb.com/docs/atlas/atlas-search/scoring/ Scoring]] - * @note Requires MongoDB 7.1 or greater + * @note Requires MongoDB 6.0.10 or greater * @since 4.11 */ def metaVectorSearchScore(fieldName: String): Bson = JProjections.metaVectorSearchScore(fieldName) diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala index 37661b1c8e7..e355a5558cc 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/VectorSearchOptions.scala @@ -22,7 +22,7 @@ import com.mongodb.client.model.search.{ VectorSearchOptions => JVectorSearchOpt * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] - * @note Requires MongoDB 7.1 or greater + * @note Requires MongoDB 6.0.10 or greater * @since 4.11 */ @Beta(Array(Beta.Reason.SERVER)) diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala index 5b7ac5030d2..e3f3fb5e308 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/package.scala @@ -223,7 +223,7 @@ package object search { * Represents optional fields of the `\$vectorSearch` pipeline stage of an aggregation pipeline. * * @see [[https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ \$vectorSearch]] - * @note Requires MongoDB 7.1 or greater + * @note Requires MongoDB 6.0.10 or greater * @since 4.11 */ @Sealed