diff --git a/driver-core/src/main/com/mongodb/client/model/search/InSearchOperator.java b/driver-core/src/main/com/mongodb/client/model/search/InSearchOperator.java new file mode 100644 index 0000000000..4540db60f1 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/InSearchOperator.java @@ -0,0 +1,43 @@ +/* + * 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.Reason; +import com.mongodb.annotations.Sealed; + +import java.util.UUID; + +import java.time.Instant; + +import org.bson.types.ObjectId; + +/** + * @see SearchOperator#in(FieldSearchPath, Boolean, Boolean...) + * @see SearchOperator#in(FieldSearchPath, ObjectId, ObjectId...) + * @see SearchOperator#in(FieldSearchPath, Number, Number...) + * @see SearchOperator#in(FieldSearchPath, Instant, Instant...) + * @see SearchOperator#in(FieldSearchPath, UUID, UUID...) + * @see SearchOperator#in(FieldSearchPath, String, String...) + * @see SearchOperator#in(FieldSearchPath, Iterable) + * @since 5.3 + */ +@Sealed +@Beta(Reason.CLIENT) +public interface InSearchOperator extends SearchOperator { + @Override + InSearchOperator score(SearchScore modifier); +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/SearchConstructibleBsonElement.java b/driver-core/src/main/com/mongodb/client/model/search/SearchConstructibleBsonElement.java index 8f0b1e510c..c293e683e5 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/SearchConstructibleBsonElement.java +++ b/driver-core/src/main/com/mongodb/client/model/search/SearchConstructibleBsonElement.java @@ -31,7 +31,7 @@ final class SearchConstructibleBsonElement extends AbstractConstructibleBsonElement implements MustCompoundSearchOperator, MustNotCompoundSearchOperator, ShouldCompoundSearchOperator, FilterCompoundSearchOperator, ExistsSearchOperator, TextSearchOperator, AutocompleteSearchOperator, - NumberNearSearchOperator, DateNearSearchOperator, GeoNearSearchOperator, + NumberNearSearchOperator, DateNearSearchOperator, GeoNearSearchOperator, InSearchOperator, ValueBoostSearchScore, PathBoostSearchScore, ConstantSearchScore, FunctionSearchScore, GaussSearchScoreExpression, PathSearchScoreExpression, FacetSearchCollector, diff --git a/driver-core/src/main/com/mongodb/client/model/search/SearchOperator.java b/driver-core/src/main/com/mongodb/client/model/search/SearchOperator.java index 00961bc3c1..6d8c612151 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/SearchOperator.java +++ b/driver-core/src/main/com/mongodb/client/model/search/SearchOperator.java @@ -20,6 +20,12 @@ import com.mongodb.annotations.Sealed; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.geojson.Point; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.bson.BsonBinary; import org.bson.BsonType; import org.bson.Document; import org.bson.conversions.Bson; @@ -28,6 +34,8 @@ import java.time.Instant; import java.util.Iterator; +import org.bson.types.ObjectId; + import static com.mongodb.assertions.Assertions.isTrueArgument; import static com.mongodb.internal.Iterables.concat; import static com.mongodb.internal.client.model.Util.combineToBsonValue; @@ -292,6 +300,121 @@ static GeoNearSearchOperator near(final Point origin, final Number pivot, final .append("pivot", notNull("pivot", pivot))); } + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The boolean value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final Boolean value, final Boolean... values) { + return in(notNull("path", path), concat(notNull("value", value), values)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The objectId value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final ObjectId value, final ObjectId... values) { + return in(notNull("path", path), concat(notNull("value", value), values)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The number value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final Number value, final Number... values) { + return in(notNull("path", path), concat(notNull("value", value), values)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The instant date value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final Instant value, final Instant... values) { + return in(notNull("path", path), concat(notNull("value", value), values)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The uuid value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final UUID value, final UUID... values) { + List bsonValues = new ArrayList<>(); + bsonValues.add(new BsonBinary(value)); + if (values != null) { + for (UUID uuid : values) { + bsonValues.add(new BsonBinary(uuid)); + } + } + return in(notNull("path", path), notNull("value", bsonValues)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The string value to search for. + * @param values More fields to be searched. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final String value, final String... values) { + return in(notNull("path", path), concat(notNull("value", value), values)); + } + + /** + * Returns a {@link SearchOperator} that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param values The non-empty values to search for. Value can be either a single value or an array of values of only one of the supported BSON types and can't be a mix of different types. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/in/ in operator + */ + static InSearchOperator in(final FieldSearchPath path, final Iterable values) { + Iterator valueIterator = (Iterator) notNull("values", values).iterator(); + isTrueArgument("values must not be empty", valueIterator.hasNext()); + T firstValue = valueIterator.next(); + + Iterator typeIterator = (Iterator) notNull("values", values).iterator(); + while (typeIterator.hasNext()) { + Object element = typeIterator.next(); + isTrueArgument("values must be of same type", firstValue.getClass().isInstance(element)); + } + + return new SearchConstructibleBsonElement("in", new Document("path", notNull("path", path).toValue()) + .append("value", valueIterator.hasNext() ? values : firstValue)); + } + /** * Returns a {@link SearchOperator} that performs a search for documents containing an ordered sequence of terms. * 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 0dd8ab387d..e2b74031ed 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 @@ -80,6 +80,7 @@ import static com.mongodb.client.model.search.SearchOperator.compound; import static com.mongodb.client.model.search.SearchOperator.dateRange; import static com.mongodb.client.model.search.SearchOperator.exists; +import static com.mongodb.client.model.search.SearchOperator.in; import static com.mongodb.client.model.search.SearchOperator.near; import static com.mongodb.client.model.search.SearchOperator.numberRange; import static com.mongodb.client.model.search.SearchOperator.phrase; @@ -610,7 +611,9 @@ private static Stream searchAndSearchMetaArgs() { .lte(Instant.ofEpochMilli(1)), near(0, 1.5, fieldPath("fieldName7"), fieldPath("fieldName8")), near(Instant.ofEpochMilli(1), Duration.ofMillis(3), fieldPath("fieldName9")), - phrase(fieldPath("fieldName10"), "term6") + phrase(fieldPath("fieldName10"), "term6"), + in(fieldPath("fieldName10"), true), + in(fieldPath("fieldName11"), "term4", "term5") )) .minimumShouldMatch(1) .mustNot(singleton( diff --git a/driver-core/src/test/unit/com/mongodb/client/model/search/SearchOperatorTest.java b/driver-core/src/test/unit/com/mongodb/client/model/search/SearchOperatorTest.java index 1b34e8cf15..b253edc661 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/search/SearchOperatorTest.java +++ b/driver-core/src/test/unit/com/mongodb/client/model/search/SearchOperatorTest.java @@ -18,14 +18,21 @@ import com.mongodb.MongoClientSettings; import com.mongodb.client.model.geojson.Point; import com.mongodb.client.model.geojson.Position; + +import java.util.UUID; + import org.bson.BsonArray; +import org.bson.BsonBinary; +import org.bson.BsonBoolean; import org.bson.BsonDateTime; import org.bson.BsonDocument; import org.bson.BsonDouble; import org.bson.BsonInt32; import org.bson.BsonInt64; +import org.bson.BsonObjectId; import org.bson.BsonString; import org.bson.Document; +import org.bson.types.ObjectId; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -581,6 +588,94 @@ void near() { ); } + @Test + void in() { + ObjectId objectId = new ObjectId(); + UUID uuid = UUID.randomUUID(); + assertAll( + () -> assertThrows(IllegalArgumentException.class, () -> + // paths must not be empty + SearchOperator.in(null, true) + ), + () -> assertThrows(IllegalArgumentException.class, () -> + // values should be of one supported BSON types and can't be a mix of different types + SearchOperator.in(fieldPath("fieldName1"), asList(true, "value")) + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonBoolean(true)) + ), + SearchOperator.in(fieldPath("fieldName1"), true) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonArray(asList(new BsonBoolean(true), new BsonBoolean(false)))) + ), + SearchOperator.in(fieldPath("fieldName1"), asList(true, false)) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonObjectId(objectId)) + ), + SearchOperator.in(fieldPath("fieldName1"), objectId) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonInt32(1)) + ), + SearchOperator.in(fieldPath("fieldName1"), 1) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonInt64(Long.MAX_VALUE)) + ), + SearchOperator.in(fieldPath("fieldName1"), Long.MAX_VALUE) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonDouble(Double.MAX_VALUE)) + ), + SearchOperator.in(fieldPath("fieldName1"), Double.MAX_VALUE) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonDateTime(Instant.EPOCH.toEpochMilli())) + ), + SearchOperator.in(fieldPath("fieldName1"), Instant.EPOCH) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonBinary(uuid)) + ), + SearchOperator.in(fieldPath("fieldName1"), uuid) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("in", + new BsonDocument("path", fieldPath("fieldName1").toBsonValue()) + .append("value", new BsonString("value")) + ), + SearchOperator.in(fieldPath("fieldName1"), "value") + .toBsonDocument() + ) + ); + } + @Test void phrase() { assertAll( diff --git a/driver-scala/src/main/scala/org/mongodb/scala/model/search/SearchOperator.scala b/driver-scala/src/main/scala/org/mongodb/scala/model/search/SearchOperator.scala index a72e5b3dbc..661234b0dd 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/model/search/SearchOperator.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/model/search/SearchOperator.scala @@ -17,10 +17,18 @@ package org.mongodb.scala.model.search import com.mongodb.annotations.{ Beta, Reason } import com.mongodb.client.model.search.{ SearchOperator => JSearchOperator } + +import java.lang.Boolean + +import java.util.UUID; + import org.mongodb.scala.bson.conversions.Bson import org.mongodb.scala.model.geojson.Point import java.time.{ Duration, Instant } + +import org.bson.types.ObjectId; + import collection.JavaConverters._ /** @@ -228,6 +236,96 @@ object SearchOperator { def near(origin: Point, pivot: Number, paths: Iterable[_ <: FieldSearchPath]): GeoNearSearchOperator = JSearchOperator.near(origin, pivot, paths.asJava) + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The boolean value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: Boolean, values: Boolean*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The objectId value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: ObjectId, values: ObjectId*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The number value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: Number, values: Number*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The instant date value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: Instant, values: Instant*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The uuid value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: UUID, values: UUID*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param value The string value to search for. + * @param values More fields to be searched. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in(path: FieldSearchPath, value: String, values: String*): InSearchOperator = + JSearchOperator.in(path, value, values: _*) + + /** + * Returns a `SearchOperator` that searches for an array of values at the given path and returns documents where the value of + * the field equals any value in the specified array. + * + * @param path The indexed field to be searched. + * @param values The non-empty values to search for. Value can be either a single value or an array of values of only one of the supported BSON types and can't be a mix of different types. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/in/ in operator]] + */ + def in[T](path: FieldSearchPath, values: Iterable[_ <: T]): InSearchOperator = + JSearchOperator.in(path, values.asJava) + /** * Returns a `SearchOperator` that performs a search for documents containing an ordered sequence of terms. * 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 e9b8640147..4ec59e2bdd 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 @@ -190,6 +190,13 @@ package object search { @Beta(Array(Reason.CLIENT)) type GeoNearSearchOperator = com.mongodb.client.model.search.GeoNearSearchOperator + /** + * @see `SearchOperator.in` + */ + @Sealed + @Beta(Array(Reason.CLIENT)) + type InSearchOperator = com.mongodb.client.model.search.InSearchOperator + /** * Fuzzy search options that may be used with some [[SearchOperator]]s. *