From 23e428f8c45d2fe0d3036c92ee78ffd3638601e2 Mon Sep 17 00:00:00 2001 From: Joy Kim Date: Mon, 23 Dec 2024 03:27:11 -0500 Subject: [PATCH 1/4] Add phrase --- .../model/search/PhraseSearchOperator.java | 58 +++++++++++++ .../SearchConstructibleBsonElement.java | 14 +++- .../client/model/search/SearchOperator.java | 30 +++++++ .../AggregatesSearchIntegrationTest.java | 4 +- .../model/search/SearchOperatorTest.java | 82 +++++++++++++++++++ .../scala/model/search/SearchOperator.scala | 21 +++++ .../mongodb/scala/model/search/package.scala | 8 ++ 7 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java diff --git a/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java b/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java new file mode 100644 index 0000000000..1daaea893a --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java @@ -0,0 +1,58 @@ +/* + * 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; + +/** + * @see SearchOperator#phrase(SearchPath, String) + * @see SearchOperator#phrase(Iterable, Iterable) + * @since 5.3 + */ + +@Sealed +@Beta(Reason.CLIENT) +public interface PhraseSearchOperator extends SearchOperator { + @Override + PhraseSearchOperator score(SearchScore modifier); + + /** + * Creates a new {@link PhraseSearchOperator} that uses slop. + * + * @return A new {@link PhraseSearchOperator}. + */ + PhraseSearchOperator slop(); + + /** + * Creates a new {@link PhraseSearchOperator} that uses slop. The default value is 0. + * + * @param slop The allowable distance between words in the query phrase. + * @return A new {@link PhraseSearchOperator}. + */ + PhraseSearchOperator slop(int slop); + + /** + * Creates a new {@link PhraseSearchOperator} that uses synonyms. + * + * @param name The name of the synonym mapping. + * @return A new {@link PhraseSearchOperator}. + * + * @mongodb.atlas.manual atlas-search/synonyms/ Synonym mappings + */ + PhraseSearchOperator synonyms(String name); +} 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..06c6a1825c 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, PhraseSearchOperator, ValueBoostSearchScore, PathBoostSearchScore, ConstantSearchScore, FunctionSearchScore, GaussSearchScoreExpression, PathSearchScoreExpression, FacetSearchCollector, @@ -81,13 +81,23 @@ public SearchConstructibleBsonElement fuzzy(final FuzzySearchOptions options) { } @Override - public TextSearchOperator synonyms(final String name) { + public SearchConstructibleBsonElement synonyms(final String name) { return newWithMutatedValue(doc -> { doc.remove("fuzzy"); doc.append("synonyms", notNull("name", name)); }); } + @Override + public PhraseSearchOperator slop() { + return slop(0); + } + + @Override + public PhraseSearchOperator slop(final int slop) { + return newWithAppendedValue("slop", notNull("slop", slop)); + } + @Override public AutocompleteSearchOperator anyTokenOrder() { return newWithAppendedValue("tokenOrder", "any"); 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 9234db91c5..53acda3373 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 @@ -292,6 +292,36 @@ static GeoNearSearchOperator near(final Point origin, final Number pivot, final .append("pivot", notNull("pivot", pivot))); } + /** + * Returns a {@link SearchOperator} that performs a search for documents containing an ordered sequence of terms. + * + * @param path The field to be searched. + * @param query The string to search for. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/phrase/ phrase operator + */ + static PhraseSearchOperator phrase(final SearchPath path, final String query) { + return phrase(singleton(notNull("path", path)), singleton(notNull("query", query))); + } + + /** + * Returns a {@link SearchOperator} that performs a search for documents containing an ordered sequence of terms. + * + * @param paths The non-empty fields to be searched. + * @param queries The non-empty strings to search for. + * @return The requested {@link SearchOperator}. + * @mongodb.atlas.manual atlas-search/phrase/ phrase operator + */ + static PhraseSearchOperator phrase(final Iterable paths, final Iterable queries) { + Iterator pathIterator = notNull("paths", paths).iterator(); + isTrueArgument("paths must not be empty", pathIterator.hasNext()); + Iterator queryIterator = notNull("queries", queries).iterator(); + isTrueArgument("queries must not be empty", queryIterator.hasNext()); + String firstQuery = queryIterator.next(); + return new SearchConstructibleBsonElement("phrase", new Document("path", combineToBsonValue(pathIterator, false)) + .append("query", queryIterator.hasNext() ? queries : firstQuery)); + } + /** * Creates a {@link SearchOperator} from a {@link Bson} in situations when there is no builder method that better satisfies your needs. * This method cannot be used to validate the syntax. 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 29de80dda3..0dd8ab387d 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 @@ -82,6 +82,7 @@ import static com.mongodb.client.model.search.SearchOperator.exists; 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; import static com.mongodb.client.model.search.SearchOperator.text; import static com.mongodb.client.model.search.SearchOptions.searchOptions; import static com.mongodb.client.model.search.SearchPath.fieldPath; @@ -608,7 +609,8 @@ private static Stream searchAndSearchMetaArgs() { dateRange(fieldPath("fieldName6")) .lte(Instant.ofEpochMilli(1)), near(0, 1.5, fieldPath("fieldName7"), fieldPath("fieldName8")), - near(Instant.ofEpochMilli(1), Duration.ofMillis(3), fieldPath("fieldName9")) + near(Instant.ofEpochMilli(1), Duration.ofMillis(3), fieldPath("fieldName9")), + phrase(fieldPath("fieldName10"), "term6") )) .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 c0ea645fb7..4cf06f6954 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 @@ -581,6 +581,88 @@ void near() { ); } + @Test + void phrase() { + assertAll( + () -> assertThrows(IllegalArgumentException.class, () -> + // queries must not be empty + SearchOperator.phrase(singleton(fieldPath("fieldName")), emptyList()) + ), + () -> assertThrows(IllegalArgumentException.class, () -> + // paths must not be empty + SearchOperator.phrase(emptyList(), singleton("term")) + ), + () -> assertEquals( + new BsonDocument("phrase", + new BsonDocument("path", fieldPath("fieldName").toBsonValue()) + .append("query", new BsonString("term")) + ), + SearchOperator.phrase( + fieldPath("fieldName"), + "term") + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("phrase", + new BsonDocument("path", new BsonArray(asList( + fieldPath("fieldName").toBsonValue(), + wildcardPath("wildc*rd").toBsonValue()))) + .append("query", new BsonArray(asList( + new BsonString("term1"), + new BsonString("term2")))) + ), + SearchOperator.phrase( + asList( + fieldPath("fieldName"), + wildcardPath("wildc*rd")), + asList( + "term1", + "term2")) + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("phrase", + new BsonDocument("path", fieldPath("fieldName").toBsonValue()) + .append("query", new BsonString("term")) + .append("synonyms", new BsonString("synonymMappingName")) + ), + SearchOperator.phrase( + singleton(fieldPath("fieldName")), + singleton("term")) + .synonyms("synonymMappingName") + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("phrase", + new BsonDocument("path", fieldPath("fieldName").toBsonValue()) + .append("query", new BsonString("term")) + .append("synonyms", new BsonString("synonymMappingName")) + .append("slop", new BsonInt32(0)) + ), + SearchOperator.phrase( + singleton(fieldPath("fieldName")), + singleton("term")) + .synonyms("synonymMappingName") + .slop() + .toBsonDocument() + ), + () -> assertEquals( + new BsonDocument("phrase", + new BsonDocument("path", fieldPath("fieldName").toBsonValue()) + .append("query", new BsonString("term")) + .append("synonyms", new BsonString("synonymMappingName")) + .append("slop", new BsonInt32(5)) + ), + SearchOperator.phrase( + singleton(fieldPath("fieldName")), + singleton("term")) + .synonyms("synonymMappingName") + .slop(5) + .toBsonDocument() + ) + ); + } + private static SearchOperator docExamplePredefined() { return SearchOperator.exists( fieldPath("fieldName")); 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 90f27092eb..a72e5b3dbc 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 @@ -228,6 +228,27 @@ object SearchOperator { def near(origin: Point, pivot: Number, paths: Iterable[_ <: FieldSearchPath]): GeoNearSearchOperator = JSearchOperator.near(origin, pivot, paths.asJava) + /** + * Returns a `SearchOperator` that performs a search for documents containing an ordered sequence of terms. + * + * @param path The field to be searched. + * @param query The string to search for. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/phrase/ phrase operator]] + */ + def phrase(path: SearchPath, query: String): PhraseSearchOperator = JSearchOperator.phrase(path, query) + + /** + * Returns a `SearchOperator` that performs a search for documents containing an ordered sequence of terms. + * + * @param paths The non-empty fields to be searched. + * @param queries The non-empty strings to search for. + * @return The requested `SearchOperator`. + * @see [[https://www.mongodb.com/docs/atlas/atlas-search/phrase/ phrase operator]] + */ + def phrase(paths: Iterable[_ <: SearchPath], queries: Iterable[String]): PhraseSearchOperator = + JSearchOperator.phrase(paths.asJava, queries.asJava) + /** * Creates a `SearchOperator` from a `Bson` in situations when there is no builder method that better satisfies your needs. * This method cannot be used to validate the syntax. 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 557060324c..e9b8640147 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 @@ -119,6 +119,14 @@ package object search { @Beta(Array(Reason.CLIENT)) type TextSearchOperator = com.mongodb.client.model.search.TextSearchOperator + /** + * @see `SearchOperator.phrase(String, SearchPath)` + * @see `SearchOperator.phrase(Iterable, Iterable)` + */ + @Sealed + @Beta(Array(Reason.CLIENT)) + type PhraseSearchOperator = com.mongodb.client.model.search.PhraseSearchOperator + /** * @see `SearchOperator.autocomplete(String, FieldSearchPath)` * @see `SearchOperator.autocomplete(Iterable, FieldSearchPath)` From 911142a36f7b9858294f7f5a063c001d96088f57 Mon Sep 17 00:00:00 2001 From: Joy Kim Date: Mon, 13 Jan 2025 11:46:04 -0500 Subject: [PATCH 2/4] update --- .../PhraseConstructibleBsonElement.java | 56 +++++++++++++++++++ .../model/search/PhraseSearchOperator.java | 7 --- .../SearchConstructibleBsonElement.java | 14 +---- .../client/model/search/SearchOperator.java | 2 +- .../model/search/SearchOperatorTest.java | 14 ----- 5 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java diff --git a/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java new file mode 100644 index 0000000000..32a4678249 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java @@ -0,0 +1,56 @@ +/* + * 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.internal.client.model.AbstractConstructibleBsonElement; + +import org.bson.conversions.Bson; + +import static com.mongodb.assertions.Assertions.notNull; + +final class PhraseConstructibleBsonElement extends AbstractConstructibleBsonElement implements + PhraseSearchOperator { + PhraseConstructibleBsonElement(final String name, final Bson value) { + super(name, value); + } + + private PhraseConstructibleBsonElement(final Bson baseElement, final Bson appendedElementValue) { + super(baseElement, appendedElementValue); + } + + @Override + protected PhraseConstructibleBsonElement newSelf(final Bson baseElement, final Bson appendedElementValue) { + return new PhraseConstructibleBsonElement(baseElement, appendedElementValue); + } + + @Override + public PhraseSearchOperator synonyms(final String name) { + return newWithMutatedValue(doc -> { + doc.remove("fuzzy"); + doc.append("synonyms", notNull("name", name)); + }); + } + + @Override + public PhraseSearchOperator slop(final int slop) { + return newWithAppendedValue("slop", notNull("slop", slop)); + } + + @Override + public PhraseConstructibleBsonElement score(final SearchScore modifier) { + return newWithAppendedValue("score", notNull("modifier", modifier)); + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java b/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java index 1daaea893a..3ac2abe05a 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java +++ b/driver-core/src/main/com/mongodb/client/model/search/PhraseSearchOperator.java @@ -31,13 +31,6 @@ public interface PhraseSearchOperator extends SearchOperator { @Override PhraseSearchOperator score(SearchScore modifier); - /** - * Creates a new {@link PhraseSearchOperator} that uses slop. - * - * @return A new {@link PhraseSearchOperator}. - */ - PhraseSearchOperator slop(); - /** * Creates a new {@link PhraseSearchOperator} that uses slop. The default value is 0. * 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 06c6a1825c..8f0b1e510c 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, PhraseSearchOperator, + NumberNearSearchOperator, DateNearSearchOperator, GeoNearSearchOperator, ValueBoostSearchScore, PathBoostSearchScore, ConstantSearchScore, FunctionSearchScore, GaussSearchScoreExpression, PathSearchScoreExpression, FacetSearchCollector, @@ -81,23 +81,13 @@ public SearchConstructibleBsonElement fuzzy(final FuzzySearchOptions options) { } @Override - public SearchConstructibleBsonElement synonyms(final String name) { + public TextSearchOperator synonyms(final String name) { return newWithMutatedValue(doc -> { doc.remove("fuzzy"); doc.append("synonyms", notNull("name", name)); }); } - @Override - public PhraseSearchOperator slop() { - return slop(0); - } - - @Override - public PhraseSearchOperator slop(final int slop) { - return newWithAppendedValue("slop", notNull("slop", slop)); - } - @Override public AutocompleteSearchOperator anyTokenOrder() { return newWithAppendedValue("tokenOrder", "any"); 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 53acda3373..00961bc3c1 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 @@ -318,7 +318,7 @@ static PhraseSearchOperator phrase(final Iterable paths, f Iterator queryIterator = notNull("queries", queries).iterator(); isTrueArgument("queries must not be empty", queryIterator.hasNext()); String firstQuery = queryIterator.next(); - return new SearchConstructibleBsonElement("phrase", new Document("path", combineToBsonValue(pathIterator, false)) + return new PhraseConstructibleBsonElement("phrase", new Document("path", combineToBsonValue(pathIterator, false)) .append("query", queryIterator.hasNext() ? queries : firstQuery)); } 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 4cf06f6954..1b34e8cf15 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 @@ -632,20 +632,6 @@ void phrase() { .synonyms("synonymMappingName") .toBsonDocument() ), - () -> assertEquals( - new BsonDocument("phrase", - new BsonDocument("path", fieldPath("fieldName").toBsonValue()) - .append("query", new BsonString("term")) - .append("synonyms", new BsonString("synonymMappingName")) - .append("slop", new BsonInt32(0)) - ), - SearchOperator.phrase( - singleton(fieldPath("fieldName")), - singleton("term")) - .synonyms("synonymMappingName") - .slop() - .toBsonDocument() - ), () -> assertEquals( new BsonDocument("phrase", new BsonDocument("path", fieldPath("fieldName").toBsonValue()) From 3f581b78042549bd483aa62e4d243ae4da8f1f2f Mon Sep 17 00:00:00 2001 From: Joy Kim Date: Mon, 13 Jan 2025 16:26:59 -0500 Subject: [PATCH 3/4] fix --- .../client/model/search/PhraseConstructibleBsonElement.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java index 32a4678249..dc70dc6038 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java +++ b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java @@ -38,10 +38,7 @@ protected PhraseConstructibleBsonElement newSelf(final Bson baseElement, final B @Override public PhraseSearchOperator synonyms(final String name) { - return newWithMutatedValue(doc -> { - doc.remove("fuzzy"); - doc.append("synonyms", notNull("name", name)); - }); + return newWithAppendedValue("synonyms", notNull("name", name)); } @Override From 313c2215f47dc12ae5ea49325d8840e93b1c8b5e Mon Sep 17 00:00:00 2001 From: Joy Kim Date: Mon, 13 Jan 2025 16:30:42 -0500 Subject: [PATCH 4/4] fix --- .../client/model/search/PhraseConstructibleBsonElement.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java index dc70dc6038..0f18e2db7a 100644 --- a/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java +++ b/driver-core/src/main/com/mongodb/client/model/search/PhraseConstructibleBsonElement.java @@ -43,7 +43,7 @@ public PhraseSearchOperator synonyms(final String name) { @Override public PhraseSearchOperator slop(final int slop) { - return newWithAppendedValue("slop", notNull("slop", slop)); + return newWithAppendedValue("slop", slop); } @Override