diff --git a/src/MongoDB.Driver/Search/MatchCriteria.cs b/src/MongoDB.Driver/Search/MatchCriteria.cs
new file mode 100644
index 00000000000..4f7b74aaf42
--- /dev/null
+++ b/src/MongoDB.Driver/Search/MatchCriteria.cs
@@ -0,0 +1,32 @@
+/* Copyright 2010-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.
+ */
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Represents the criteria used to match terms in a query for the Atlas Search Text operator.
+ ///
+ public enum MatchCriteria
+ {
+ ///
+ /// Match documents containing any of the terms from a query.
+ ///
+ Any,
+ ///
+ /// Match documents containing all the terms from a query.
+ ///
+ All
+ }
+}
\ No newline at end of file
diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
index b4686e3a815..28304fae5ad 100644
--- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
+++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
@@ -328,16 +328,17 @@ internal sealed class PhraseSearchDefinition : OperatorSearchDefiniti
{
private readonly SearchQueryDefinition _query;
private readonly int? _slop;
+ private readonly string _synonyms;
public PhraseSearchDefinition(
SearchPathDefinition path,
SearchQueryDefinition query,
- int? slop,
- SearchScoreDefinition score)
- : base(OperatorType.Phrase, path, score)
+ SearchPhraseOptions options)
+ : base(OperatorType.Phrase, path, options?.Score)
{
_query = Ensure.IsNotNull(query, nameof(query));
- _slop = slop;
+ _slop = options?.Slop;
+ _synonyms = options?.Synonyms;
}
private protected override BsonDocument RenderArguments(
@@ -345,7 +346,8 @@ private protected override BsonDocument RenderArguments(
IBsonSerializer fieldSerializer) => new()
{
{ "query", _query.Render() },
- { "slop", _slop, _slop != null }
+ { "slop", _slop, _slop != null },
+ { "synonyms", _synonyms, _synonyms != null }
};
}
@@ -461,29 +463,40 @@ private protected override BsonDocument RenderArguments(
internal sealed class TextSearchDefinition : OperatorSearchDefinition
{
private readonly SearchFuzzyOptions _fuzzy;
+ private readonly string _matchCriteria;
private readonly SearchQueryDefinition _query;
private readonly string _synonyms;
public TextSearchDefinition(
SearchPathDefinition path,
SearchQueryDefinition query,
- SearchFuzzyOptions fuzzy,
- SearchScoreDefinition score,
- string synonyms)
- : base(OperatorType.Text, path, score)
+ SearchTextOptions options)
+ : base(OperatorType.Text, path, options?.Score)
{
_query = Ensure.IsNotNull(query, nameof(query));
- _fuzzy = fuzzy;
- _synonyms = synonyms;
+ _fuzzy = options?.Fuzzy;
+ _synonyms = options?.Synonyms;
+ _matchCriteria = options?.MatchCriteria switch
+ {
+ MatchCriteria.All => "all",
+ MatchCriteria.Any => "any",
+ null => null,
+ _ => throw new ArgumentException("Invalid match criteria set for Atlas Search text operator.")
+ };
}
- private protected override BsonDocument RenderArguments(RenderArgs args,
- IBsonSerializer fieldSerializer) => new()
+ private protected override BsonDocument RenderArguments(
+ RenderArgs args,
+ IBsonSerializer fieldSerializer)
+ {
+ return new BsonDocument
{
{ "query", _query.Render() },
{ "fuzzy", () => _fuzzy.Render(), _fuzzy != null },
- { "synonyms", _synonyms, _synonyms != null }
+ { "synonyms", _synonyms, _synonyms != null },
+ { "matchCriteria", _matchCriteria, _matchCriteria != null }
};
+ }
}
internal sealed class WildcardSearchDefinition : OperatorSearchDefinition
diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs
index 09c5172a7f8..21f51069093 100644
--- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs
+++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs
@@ -550,7 +550,21 @@ public SearchDefinition Phrase(
SearchQueryDefinition query,
int? slop = null,
SearchScoreDefinition score = null) =>
- new PhraseSearchDefinition(path, query, slop, score);
+ new PhraseSearchDefinition(path, query, new SearchPhraseOptions { Slop = slop, Score = score });
+
+ ///
+ /// Creates a search definition that performs search for documents containing an ordered
+ /// sequence of terms.
+ ///
+ /// The indexed field or fields to search.
+ /// The string or strings to search for.
+ /// The options.
+ /// A phrase search definition.
+ public SearchDefinition Phrase(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ SearchPhraseOptions options) =>
+ new PhraseSearchDefinition(path, query, options);
///
/// Creates a search definition that performs search for documents containing an ordered
@@ -569,6 +583,21 @@ public SearchDefinition Phrase(
SearchScoreDefinition score = null) =>
Phrase(new ExpressionFieldDefinition(path), query, slop, score);
+ ///
+ /// Creates a search definition that performs search for documents containing an ordered
+ /// sequence of terms.
+ ///
+ /// The type of the field.
+ /// The indexed field or fields to search.
+ /// The string or strings to search for.
+ /// The options.
+ /// A phrase search definition.
+ public SearchDefinition Phrase(
+ Expression> path,
+ SearchQueryDefinition query,
+ SearchPhraseOptions options) =>
+ Phrase(new ExpressionFieldDefinition(path), query, options);
+
///
/// Creates a search definition that queries a combination of indexed fields and values.
///
@@ -732,6 +761,20 @@ public SearchDefinition Regex(
public SearchDefinition Span(SearchSpanDefinition clause) =>
new SpanSearchDefinition(clause);
+ ///
+ /// Creates a search definition that performs full-text search using the analyzer specified
+ /// in the index configuration.
+ ///
+ /// The indexed field or fields to search.
+ /// The string or strings to search for.
+ /// The options.
+ /// A text search definition.
+ public SearchDefinition Text(
+ SearchPathDefinition path,
+ SearchQueryDefinition query,
+ SearchTextOptions options) =>
+ new TextSearchDefinition(path, query, options);
+
///
/// Creates a search definition that performs full-text search using the analyzer specified
/// in the index configuration.
@@ -746,7 +789,7 @@ public SearchDefinition Text(
SearchQueryDefinition query,
SearchFuzzyOptions fuzzy = null,
SearchScoreDefinition score = null) =>
- new TextSearchDefinition(path, query, fuzzy, score, null);
+ new TextSearchDefinition(path, query, new SearchTextOptions { Fuzzy = fuzzy, Score = score });
///
/// Creates a search definition that performs full-text search with synonyms using the analyzer specified
@@ -762,7 +805,22 @@ public SearchDefinition Text(
SearchQueryDefinition query,
string synonyms,
SearchScoreDefinition score = null) =>
- new TextSearchDefinition(path, query, null, score, synonyms);
+ new TextSearchDefinition(path, query, new SearchTextOptions { Score = score, Synonyms = synonyms });
+
+ ///
+ /// Creates a search definition that performs full-text search using the analyzer specified
+ /// in the index configuration.
+ ///
+ /// The type of the field.
+ /// The indexed field or field to search.
+ /// The string or strings to search for.
+ /// The options.
+ /// A text search definition.
+ public SearchDefinition Text(
+ Expression> path,
+ SearchQueryDefinition query,
+ SearchTextOptions options) =>
+ Text(new ExpressionFieldDefinition(path), query, options);
///
/// Creates a search definition that performs full-text search using the analyzer specified
diff --git a/src/MongoDB.Driver/Search/SearchPhraseOptions.cs b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs
new file mode 100644
index 00000000000..8c239a1323e
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs
@@ -0,0 +1,38 @@
+/* Copyright 2010-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.
+ */
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Options for atlas search phrase operator.
+ ///
+ public sealed class SearchPhraseOptions
+ {
+ ///
+ /// The score modifier.
+ ///
+ public SearchScoreDefinition Score { get; set; }
+
+ ///
+ /// The allowable distance between words in the query phrase.
+ ///
+ public int? Slop { get; set; }
+
+ ///
+ /// The name of the synonym mapping definition in the index definition. Value can't be an empty string (e.g. "").
+ ///
+ public string Synonyms { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/MongoDB.Driver/Search/SearchTextOptions.cs b/src/MongoDB.Driver/Search/SearchTextOptions.cs
new file mode 100644
index 00000000000..ceb09e564a9
--- /dev/null
+++ b/src/MongoDB.Driver/Search/SearchTextOptions.cs
@@ -0,0 +1,44 @@
+/* Copyright 2010-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.
+ */
+
+namespace MongoDB.Driver.Search
+{
+ ///
+ /// Options for atlas search text operator.
+ ///
+ public sealed class SearchTextOptions
+ {
+ ///
+ /// The options for fuzzy search.
+ ///
+ public SearchFuzzyOptions Fuzzy { get; set; }
+
+ ///
+ /// The criteria to use to match the terms in the query. Value can be either "any" or "all".
+ /// Defaults to "all" if omitted.
+ ///
+ public MatchCriteria? MatchCriteria { get; set; }
+
+ ///
+ /// The score modifier.
+ ///
+ public SearchScoreDefinition Score { get; set; }
+
+ ///
+ /// The name of the synonym mapping definition in the index definition. Value can't be an empty string (e.g. "").
+ ///
+ public string Synonyms { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs
index 0ea49ce015e..b014e9152a8 100644
--- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs
+++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs
@@ -421,6 +421,26 @@ public void PhraseAnalyzerPath()
result.Title.Should().Be("Declaration of Independence");
}
+ [Fact]
+ public void PhraseSynonym()
+ {
+ var result =
+ GetSynonymTestCollection().Aggregate()
+ .Search(
+ Builders.Search.Phrase("plot", "automobile race", new SearchPhraseOptions { Synonyms = "transportSynonyms" }),
+ indexName: "synonyms-tests")
+ .Project(Builders.Projection.Include("Title").Exclude("_id"))
+ .Limit(5)
+ .ToList();
+
+ result.Count.Should().Be(5);
+ result[0].Title.Should().Be("The Great Race");
+ result[1].Title.Should().Be("The Cannonball Run");
+ result[2].Title.Should().Be("National Mechanics");
+ result[3].Title.Should().Be("Genevieve");
+ result[4].Title.Should().Be("Speedway Junky");
+ }
+
[Fact]
public void PhraseWildcardPath()
{
@@ -723,6 +743,26 @@ public void Text()
result.Title.Should().Be("Declaration of Independence");
}
+ [Fact]
+ public void TextMatchCriteria()
+ {
+ var result =
+ GetSynonymTestCollection().Aggregate()
+ .Search(
+ Builders.Search.Text("plot", "attire", new SearchTextOptions { Synonyms = "attireSynonyms", MatchCriteria = MatchCriteria.Any}),
+ indexName: "synonyms-tests")
+ .Project(Builders.Projection.Include("Title").Exclude("_id"))
+ .Limit(5)
+ .ToList();
+
+ result.Count.Should().Be(5);
+ result[0].Title.Should().Be("The Royal Tailor");
+ result[1].Title.Should().Be("La guerre des tuques");
+ result[2].Title.Should().Be("The Dress");
+ result[3].Title.Should().Be("The Club");
+ result[4].Title.Should().Be("The Triple Echo");
+ }
+
[Theory]
[InlineData("automobile", "transportSynonyms", "Blue Car")]
[InlineData("boat", "transportSynonyms", "And the Ship Sails On")]
diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs
index 7e06af49c7b..0afd28ea4ee 100644
--- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs
+++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs
@@ -949,10 +949,22 @@ public void Phrase()
subject.Phrase("x", "foo", 5),
"{ phrase: { query: 'foo', path: 'x', slop: 5 } }");
+ AssertRendered(
+ subject.Phrase("x", "foo", new SearchPhraseOptions { Synonyms = "testSynonyms" }),
+ "{ phrase: { query: 'foo', path: 'x', synonyms: 'testSynonyms' } }");
+
+ AssertRendered(
+ subject.Phrase("x", "foo", 5),
+ "{ phrase: { query: 'foo', path: 'x', slop: 5 } }");
+
var scoreBuilder = new SearchScoreDefinitionBuilder();
AssertRendered(
subject.Phrase("x", "foo", score: scoreBuilder.Constant(1)),
"{ phrase: { query: 'foo', path: 'x', score: { constant: { value: 1 } } } }");
+
+ AssertRendered(
+ subject.Phrase("x", "foo", new SearchPhraseOptions { Score = scoreBuilder.Constant(1), Slop = 5}),
+ "{ phrase: { query: 'foo', slop: 5, path: 'x', score: { constant: { value: 1 } } } }");
}
[Fact]
@@ -970,6 +982,10 @@ public void Phrase_typed()
subject.Phrase(x => x.Hobbies, "foo"),
"{ phrase: { query: 'foo', path: 'hobbies' } }");
+ AssertRendered(
+ subject.Phrase(x => x.FirstName, "foo", new SearchPhraseOptions { Synonyms = "testSynonyms" }),
+ "{ phrase: { query: 'foo', synonyms: 'testSynonyms', path: 'fn' } }");
+
AssertRendered(
subject.Phrase(
new FieldDefinition[]
@@ -1308,6 +1324,10 @@ public void Text()
subject.Text(new[] { "x", "y" }, new[] { "foo", "bar" }, "testSynonyms"),
"{ text: { query: ['foo', 'bar'], synonyms: 'testSynonyms', path: ['x', 'y'] } }");
+ AssertRendered(
+ subject.Text(new[] { "x", "y" }, new[] { "foo", "bar" }, new SearchTextOptions{ MatchCriteria = MatchCriteria.Any }),
+ "{ text: { query: ['foo', 'bar'], matchCriteria: 'any', path: ['x', 'y'] } }");
+
AssertRendered(
subject.Text("x", "foo", new SearchFuzzyOptions()),
"{ text: { query: 'foo', path: 'x', fuzzy: {} } }");
@@ -1327,6 +1347,21 @@ public void Text()
AssertRendered(
subject.Text("x", "foo", "testSynonyms", scoreBuilder.Constant(1)),
"{ text: { query: 'foo', synonyms: 'testSynonyms', path: 'x', score: { constant: { value: 1 } } } }");
+
+ AssertRendered(
+ subject.Text("x", "foo", new SearchTextOptions {Score = scoreBuilder.Constant(1), MatchCriteria = MatchCriteria.All}),
+ "{ text: { query: 'foo', matchCriteria: 'all', path: 'x', score: { constant: { value: 1 } } } }");
+ }
+
+ [Fact]
+ public void Text_should_throw_with_invalid_options()
+ {
+ var subject = CreateSubject();
+
+ Action act = () =>
+ subject.Text("x", "foo", new SearchTextOptions { MatchCriteria = (MatchCriteria)3 });
+
+ act.ShouldThrow();
}
[Fact]
@@ -1337,6 +1372,9 @@ public void Text_typed()
AssertRendered(
subject.Text(x => x.FirstName, "foo"),
"{ text: { query: 'foo', path: 'fn' } }");
+ AssertRendered(
+ subject.Text(x => x.FirstName, "foo", new SearchTextOptions { MatchCriteria = MatchCriteria.All}),
+ "{ text: { query: 'foo', matchCriteria: 'all', path: 'fn' } }");
AssertRendered(
subject.Text("FirstName", "foo"),
"{ text: { query: 'foo', path: 'fn' } }");