From 494a486d7343e663c2b10c5e0e34e6f4e0279918 Mon Sep 17 00:00:00 2001 From: adelinowona Date: Thu, 1 May 2025 01:03:27 -0400 Subject: [PATCH 01/10] CSHARP-5543: Add new options for Atlas Search Text and Phrase operators # Conflicts: # src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs --- .../Search/OperatorSearchDefinitions.cs | 40 +++++++++++-- .../Search/SearchDefinitionBuilder.cs | 58 +++++++++++++++++++ .../Search/SearchPhraseOptions.cs | 38 ++++++++++++ .../Search/SearchTextOptions.cs | 44 ++++++++++++++ .../Search/AtlasSearchTests.cs | 40 +++++++++++++ .../Search/SearchDefinitionBuilderTests.cs | 27 +++++++++ 6 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 src/MongoDB.Driver/Search/SearchPhraseOptions.cs create mode 100644 src/MongoDB.Driver/Search/SearchTextOptions.cs diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index b4686e3a815..399c0a92bb1 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -328,6 +328,7 @@ internal sealed class PhraseSearchDefinition : OperatorSearchDefiniti { private readonly SearchQueryDefinition _query; private readonly int? _slop; + private readonly string _synonyms; public PhraseSearchDefinition( SearchPathDefinition path, @@ -340,12 +341,25 @@ public PhraseSearchDefinition( _slop = slop; } + public PhraseSearchDefinition( + SearchPathDefinition path, + SearchQueryDefinition query, + SearchPhraseOptions options) + : base(OperatorType.Phrase, path, options?.Score) + { + _query = Ensure.IsNotNull(query, nameof(query)); + _slop = options?.Slop; + _synonyms = options?.Synonyms; + } + private protected override BsonDocument RenderArguments( RenderArgs args, - IBsonSerializer fieldSerializer) => new() + IBsonSerializer fieldSerializer) => + new() { { "query", _query.Render() }, - { "slop", _slop, _slop != null } + { "slop", _slop, _slop != null }, + { "synonyms", _synonyms, _synonyms != null } }; } @@ -463,6 +477,7 @@ internal sealed class TextSearchDefinition : OperatorSearchDefinition private readonly SearchFuzzyOptions _fuzzy; private readonly SearchQueryDefinition _query; private readonly string _synonyms; + private readonly string _matchCriteria; public TextSearchDefinition( SearchPathDefinition path, @@ -477,12 +492,27 @@ public TextSearchDefinition( _synonyms = synonyms; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer) => new() + public TextSearchDefinition( + SearchPathDefinition path, + SearchQueryDefinition query, + SearchTextOptions options) + : base(OperatorType.Text, path, options?.Score) + { + _query = Ensure.IsNotNull(query, nameof(query)); + _fuzzy = options?.Fuzzy; + _synonyms = options?.Synonyms; + _matchCriteria = options?.MatchCriteria; + } + + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => + new() { { "query", _query.Render() }, { "fuzzy", () => _fuzzy.Render(), _fuzzy != null }, - { "synonyms", _synonyms, _synonyms != null } + { "synonyms", _synonyms, _synonyms != null }, + { "matchCriteria", _matchCriteria, _matchCriteria != null } }; } diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 09c5172a7f8..1fc4e49475a 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -552,6 +552,20 @@ public SearchDefinition Phrase( SearchScoreDefinition score = null) => new PhraseSearchDefinition(path, query, slop, 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 /// sequence of terms. @@ -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. @@ -764,6 +807,21 @@ public SearchDefinition Text( SearchScoreDefinition score = null) => new TextSearchDefinition(path, query, null, score, 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 /// in the index configuration. diff --git a/src/MongoDB.Driver/Search/SearchPhraseOptions.cs b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs new file mode 100644 index 00000000000..c5e4165e9d7 --- /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 allowable distance between words in the query phrase. + /// + public int? Slop { 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. + /// + 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..247da2a5deb --- /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 string 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. + /// + 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..0fc0507ccb7 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 = "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..ce37e49e281 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 = "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,10 @@ 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 = "all"}), + "{ text: { query: 'foo', matchCriteria: 'all', path: 'x', score: { constant: { value: 1 } } } }"); } [Fact] @@ -1337,6 +1361,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 = "all"}), + "{ text: { query: 'foo', matchCriteria: 'all', path: 'fn' } }"); AssertRendered( subject.Text("FirstName", "foo"), "{ text: { query: 'foo', path: 'fn' } }"); From c563ebe04c2e9a317009a61549593be1c0869538 Mon Sep 17 00:00:00 2001 From: adelinowona Date: Mon, 28 Apr 2025 17:54:10 -0400 Subject: [PATCH 02/10] put options in alphabetical order --- src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs | 2 +- src/MongoDB.Driver/Search/SearchPhraseOptions.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 399c0a92bb1..0cc3d8d4a68 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -475,9 +475,9 @@ 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; - private readonly string _matchCriteria; public TextSearchDefinition( SearchPathDefinition path, diff --git a/src/MongoDB.Driver/Search/SearchPhraseOptions.cs b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs index c5e4165e9d7..407a74d7e7c 100644 --- a/src/MongoDB.Driver/Search/SearchPhraseOptions.cs +++ b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs @@ -21,14 +21,14 @@ namespace MongoDB.Driver.Search public sealed class SearchPhraseOptions { /// - /// The allowable distance between words in the query phrase. + /// The score modifier. /// - public int? Slop { get; set; } + public SearchScoreDefinition Score { get; set; } /// - /// The score modifier. + /// The allowable distance between words in the query phrase. /// - public SearchScoreDefinition Score { get; set; } + public int? Slop { get; set; } /// /// The name of the synonym mapping definition in the index definition. Value can't be an empty string. From 5e845d6a33a742553b6a63a9ca478c9d7d90c29a Mon Sep 17 00:00:00 2001 From: adelinowona Date: Mon, 28 Apr 2025 18:15:43 -0400 Subject: [PATCH 03/10] make matchCriteria an enum --- src/MongoDB.Driver/MatchCriteria.cs | 32 +++++++++++++++++++ .../Search/OperatorSearchDefinitions.cs | 4 +-- .../Search/SearchTextOptions.cs | 2 +- .../Search/AtlasSearchTests.cs | 2 +- .../Search/SearchDefinitionBuilderTests.cs | 6 ++-- 5 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 src/MongoDB.Driver/MatchCriteria.cs diff --git a/src/MongoDB.Driver/MatchCriteria.cs b/src/MongoDB.Driver/MatchCriteria.cs new file mode 100644 index 00000000000..aa3462f358a --- /dev/null +++ b/src/MongoDB.Driver/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 +{ + /// + /// 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 0cc3d8d4a68..d759d1632d7 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -475,7 +475,7 @@ private protected override BsonDocument RenderArguments( internal sealed class TextSearchDefinition : OperatorSearchDefinition { private readonly SearchFuzzyOptions _fuzzy; - private readonly string _matchCriteria; + private readonly MatchCriteria? _matchCriteria; private readonly SearchQueryDefinition _query; private readonly string _synonyms; @@ -512,7 +512,7 @@ private protected override BsonDocument RenderArguments( { "query", _query.Render() }, { "fuzzy", () => _fuzzy.Render(), _fuzzy != null }, { "synonyms", _synonyms, _synonyms != null }, - { "matchCriteria", _matchCriteria, _matchCriteria != null } + { "matchCriteria", _matchCriteria == MatchCriteria.Any ? "any" : "all", _matchCriteria != null } }; } diff --git a/src/MongoDB.Driver/Search/SearchTextOptions.cs b/src/MongoDB.Driver/Search/SearchTextOptions.cs index 247da2a5deb..fdc5d3e488b 100644 --- a/src/MongoDB.Driver/Search/SearchTextOptions.cs +++ b/src/MongoDB.Driver/Search/SearchTextOptions.cs @@ -29,7 +29,7 @@ public sealed class SearchTextOptions /// The criteria to use to match the terms in the query. Value can be either "any" or "all". /// Defaults to "all" if omitted. /// - public string MatchCriteria { get; set; } + public MatchCriteria MatchCriteria { get; set; } /// /// The score modifier. diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs index 0fc0507ccb7..b014e9152a8 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs @@ -749,7 +749,7 @@ public void TextMatchCriteria() var result = GetSynonymTestCollection().Aggregate() .Search( - Builders.Search.Text("plot", "attire", new SearchTextOptions { Synonyms = "attireSynonyms", MatchCriteria = "any"}), + Builders.Search.Text("plot", "attire", new SearchTextOptions { Synonyms = "attireSynonyms", MatchCriteria = MatchCriteria.Any}), indexName: "synonyms-tests") .Project(Builders.Projection.Include("Title").Exclude("_id")) .Limit(5) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index ce37e49e281..6ad97715287 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1325,7 +1325,7 @@ public void Text() "{ text: { query: ['foo', 'bar'], synonyms: 'testSynonyms', path: ['x', 'y'] } }"); AssertRendered( - subject.Text(new[] { "x", "y" }, new[] { "foo", "bar" }, new SearchTextOptions{ MatchCriteria = "any" }), + subject.Text(new[] { "x", "y" }, new[] { "foo", "bar" }, new SearchTextOptions{ MatchCriteria = MatchCriteria.Any }), "{ text: { query: ['foo', 'bar'], matchCriteria: 'any', path: ['x', 'y'] } }"); AssertRendered( @@ -1349,7 +1349,7 @@ public void Text() "{ text: { query: 'foo', synonyms: 'testSynonyms', path: 'x', score: { constant: { value: 1 } } } }"); AssertRendered( - subject.Text("x", "foo", new SearchTextOptions {Score = scoreBuilder.Constant(1), MatchCriteria = "all"}), + subject.Text("x", "foo", new SearchTextOptions {Score = scoreBuilder.Constant(1), MatchCriteria = MatchCriteria.All}), "{ text: { query: 'foo', matchCriteria: 'all', path: 'x', score: { constant: { value: 1 } } } }"); } @@ -1362,7 +1362,7 @@ public void Text_typed() subject.Text(x => x.FirstName, "foo"), "{ text: { query: 'foo', path: 'fn' } }"); AssertRendered( - subject.Text(x => x.FirstName, "foo", new SearchTextOptions { MatchCriteria = "all"}), + subject.Text(x => x.FirstName, "foo", new SearchTextOptions { MatchCriteria = MatchCriteria.All}), "{ text: { query: 'foo', matchCriteria: 'all', path: 'fn' } }"); AssertRendered( subject.Text("FirstName", "foo"), From 573fcc1980594c6aebf03514ffb9a577789b90a4 Mon Sep 17 00:00:00 2001 From: adelinowona Date: Tue, 29 Apr 2025 16:07:02 -0400 Subject: [PATCH 04/10] address pr comments --- .../{ => Search}/MatchCriteria.cs | 2 +- .../Search/OperatorSearchDefinitions.cs | 24 ------------------- .../Search/SearchDefinitionBuilder.cs | 6 ++--- .../Search/SearchTextOptions.cs | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) rename src/MongoDB.Driver/{ => Search}/MatchCriteria.cs (96%) diff --git a/src/MongoDB.Driver/MatchCriteria.cs b/src/MongoDB.Driver/Search/MatchCriteria.cs similarity index 96% rename from src/MongoDB.Driver/MatchCriteria.cs rename to src/MongoDB.Driver/Search/MatchCriteria.cs index aa3462f358a..4f7b74aaf42 100644 --- a/src/MongoDB.Driver/MatchCriteria.cs +++ b/src/MongoDB.Driver/Search/MatchCriteria.cs @@ -13,7 +13,7 @@ * limitations under the License. */ -namespace MongoDB.Driver +namespace MongoDB.Driver.Search { /// /// Represents the criteria used to match terms in a query for the Atlas Search Text operator. diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index d759d1632d7..f17ac372ca2 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -330,17 +330,6 @@ internal sealed class PhraseSearchDefinition : OperatorSearchDefiniti private readonly int? _slop; private readonly string _synonyms; - public PhraseSearchDefinition( - SearchPathDefinition path, - SearchQueryDefinition query, - int? slop, - SearchScoreDefinition score) - : base(OperatorType.Phrase, path, score) - { - _query = Ensure.IsNotNull(query, nameof(query)); - _slop = slop; - } - public PhraseSearchDefinition( SearchPathDefinition path, SearchQueryDefinition query, @@ -479,19 +468,6 @@ internal sealed class TextSearchDefinition : OperatorSearchDefinition 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) - { - _query = Ensure.IsNotNull(query, nameof(query)); - _fuzzy = fuzzy; - _synonyms = synonyms; - } - public TextSearchDefinition( SearchPathDefinition path, SearchQueryDefinition query, diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 1fc4e49475a..3f4d12198dd 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -550,7 +550,7 @@ 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 @@ -789,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 @@ -805,7 +805,7 @@ 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 diff --git a/src/MongoDB.Driver/Search/SearchTextOptions.cs b/src/MongoDB.Driver/Search/SearchTextOptions.cs index fdc5d3e488b..f806f0da1fa 100644 --- a/src/MongoDB.Driver/Search/SearchTextOptions.cs +++ b/src/MongoDB.Driver/Search/SearchTextOptions.cs @@ -29,7 +29,7 @@ public sealed class SearchTextOptions /// 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; } + public MatchCriteria? MatchCriteria { get; set; } /// /// The score modifier. From a7a6fa5a02452b370d2b555a15cd1b9e69f4180a Mon Sep 17 00:00:00 2001 From: adelinowona Date: Wed, 30 Apr 2025 12:53:58 -0400 Subject: [PATCH 05/10] fix indentation and clarify api docs --- src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs | 8 ++++---- src/MongoDB.Driver/Search/SearchPhraseOptions.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 3f4d12198dd..21f51069093 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -564,7 +564,7 @@ public SearchDefinition Phrase( SearchPathDefinition path, SearchQueryDefinition query, SearchPhraseOptions options) => - new PhraseSearchDefinition(path, query, options); + new PhraseSearchDefinition(path, query, options); /// /// Creates a search definition that performs search for documents containing an ordered @@ -596,7 +596,7 @@ public SearchDefinition Phrase( Expression> path, SearchQueryDefinition query, SearchPhraseOptions options) => - Phrase(new ExpressionFieldDefinition(path), query, options); + Phrase(new ExpressionFieldDefinition(path), query, options); /// /// Creates a search definition that queries a combination of indexed fields and values. @@ -773,7 +773,7 @@ public SearchDefinition Text( SearchPathDefinition path, SearchQueryDefinition query, SearchTextOptions options) => - new TextSearchDefinition(path, query, options); + new TextSearchDefinition(path, query, options); /// /// Creates a search definition that performs full-text search using the analyzer specified @@ -820,7 +820,7 @@ public SearchDefinition Text( Expression> path, SearchQueryDefinition query, SearchTextOptions options) => - Text(new ExpressionFieldDefinition(path), query, 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 index 407a74d7e7c..8c239a1323e 100644 --- a/src/MongoDB.Driver/Search/SearchPhraseOptions.cs +++ b/src/MongoDB.Driver/Search/SearchPhraseOptions.cs @@ -31,7 +31,7 @@ public sealed class SearchPhraseOptions public int? Slop { get; set; } /// - /// The name of the synonym mapping definition in the index definition. Value can't be an empty string. + /// 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; } } From b75bcc8b559199655f831c9dcedba4e106d72587 Mon Sep 17 00:00:00 2001 From: adelinowona Date: Wed, 30 Apr 2025 16:12:36 -0400 Subject: [PATCH 06/10] fix indentation and more robust matchCriteria checks --- .../Search/OperatorSearchDefinitions.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index f17ac372ca2..685031d3ce0 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -334,7 +334,7 @@ public PhraseSearchDefinition( SearchPathDefinition path, SearchQueryDefinition query, SearchPhraseOptions options) - : base(OperatorType.Phrase, path, options?.Score) + : base(OperatorType.Phrase, path, options?.Score) { _query = Ensure.IsNotNull(query, nameof(query)); _slop = options?.Slop; @@ -472,7 +472,7 @@ public TextSearchDefinition( SearchPathDefinition path, SearchQueryDefinition query, SearchTextOptions options) - : base(OperatorType.Text, path, options?.Score) + : base(OperatorType.Text, path, options?.Score) { _query = Ensure.IsNotNull(query, nameof(query)); _fuzzy = options?.Fuzzy; @@ -488,7 +488,15 @@ private protected override BsonDocument RenderArguments( { "query", _query.Render() }, { "fuzzy", () => _fuzzy.Render(), _fuzzy != null }, { "synonyms", _synonyms, _synonyms != null }, - { "matchCriteria", _matchCriteria == MatchCriteria.Any ? "any" : "all", _matchCriteria != null } + { + "matchCriteria", () => _matchCriteria switch + { + MatchCriteria.All => "all", + MatchCriteria.Any => "any", + _ => throw new ArgumentException("Invalid match criteria set for Atlas Search text operator.") + }, + _matchCriteria != null + } }; } From 71ec65b4ffd786eef927bfe57c030ce3703e63f6 Mon Sep 17 00:00:00 2001 From: adelinowona Date: Wed, 30 Apr 2025 16:30:40 -0400 Subject: [PATCH 07/10] add test for exception --- .../Search/SearchDefinitionBuilderTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 6ad97715287..c77fa4033b2 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1353,6 +1353,21 @@ public void Text() "{ text: { query: 'foo', matchCriteria: 'all', path: 'x', score: { constant: { value: 1 } } } }"); } + [Fact] + public void Text_should_throw_with_invalid_options() + { + var subject = CreateSubject(); + + var query = subject.Text("x", "foo", new SearchTextOptions { MatchCriteria = (MatchCriteria)3 }); + + Action act = () => + query.Render(new RenderArgs( + BsonSerializer.SerializerRegistry.GetSerializer(), + BsonSerializer.SerializerRegistry)); + + act.ShouldThrow(); + } + [Fact] public void Text_typed() { From d9a74340a2243214310d219031d35bb2e4650e1f Mon Sep 17 00:00:00 2001 From: adelinowona Date: Thu, 1 May 2025 01:05:38 -0400 Subject: [PATCH 08/10] avoid context capturing # Conflicts: # src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs --- .../Search/OperatorSearchDefinitions.cs | 26 +++++++++---------- .../Search/SearchDefinitionBuilderTests.cs | 6 +---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 685031d3ce0..e52824f7bd3 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -464,7 +464,7 @@ private protected override BsonDocument RenderArguments( internal sealed class TextSearchDefinition : OperatorSearchDefinition { private readonly SearchFuzzyOptions _fuzzy; - private readonly MatchCriteria? _matchCriteria; + private readonly string _matchCriteria; private readonly SearchQueryDefinition _query; private readonly string _synonyms; @@ -477,27 +477,27 @@ public TextSearchDefinition( _query = Ensure.IsNotNull(query, nameof(query)); _fuzzy = options?.Fuzzy; _synonyms = options?.Synonyms; - _matchCriteria = options?.MatchCriteria; + _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() + IBsonSerializer fieldSerializer) + { + return new BsonDocument { { "query", _query.Render() }, { "fuzzy", () => _fuzzy.Render(), _fuzzy != null }, { "synonyms", _synonyms, _synonyms != null }, - { - "matchCriteria", () => _matchCriteria switch - { - MatchCriteria.All => "all", - MatchCriteria.Any => "any", - _ => throw new ArgumentException("Invalid match criteria set for Atlas Search text operator.") - }, - _matchCriteria != null - } + { "matchCriteria", _matchCriteria, _matchCriteria != null } }; + } } internal sealed class WildcardSearchDefinition : OperatorSearchDefinition diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index c77fa4033b2..0afd28ea4ee 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1358,12 +1358,8 @@ public void Text_should_throw_with_invalid_options() { var subject = CreateSubject(); - var query = subject.Text("x", "foo", new SearchTextOptions { MatchCriteria = (MatchCriteria)3 }); - Action act = () => - query.Render(new RenderArgs( - BsonSerializer.SerializerRegistry.GetSerializer(), - BsonSerializer.SerializerRegistry)); + subject.Text("x", "foo", new SearchTextOptions { MatchCriteria = (MatchCriteria)3 }); act.ShouldThrow(); } From 24f4c85c16c7a2cae63c3279c5d3e1d95a4a972c Mon Sep 17 00:00:00 2001 From: adelinowona Date: Thu, 1 May 2025 01:07:42 -0400 Subject: [PATCH 09/10] minor indentation fix --- src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index e52824f7bd3..28304fae5ad 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -343,8 +343,7 @@ public PhraseSearchDefinition( private protected override BsonDocument RenderArguments( RenderArgs args, - IBsonSerializer fieldSerializer) => - new() + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "slop", _slop, _slop != null }, From 79bb9e03081bfe5a184a9d55ad1543c7dc37ab1b Mon Sep 17 00:00:00 2001 From: adelinowona Date: Thu, 1 May 2025 01:10:22 -0400 Subject: [PATCH 10/10] sync xml docs --- src/MongoDB.Driver/Search/SearchTextOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/Search/SearchTextOptions.cs b/src/MongoDB.Driver/Search/SearchTextOptions.cs index f806f0da1fa..ceb09e564a9 100644 --- a/src/MongoDB.Driver/Search/SearchTextOptions.cs +++ b/src/MongoDB.Driver/Search/SearchTextOptions.cs @@ -37,7 +37,7 @@ public sealed class SearchTextOptions public SearchScoreDefinition Score { get; set; } /// - /// The name of the synonym mapping definition in the index definition. Value can't be an empty string. + /// 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; } }