Skip to content

Commit d6840c3

Browse files
authored
CSHARP-5543: Add new options for Atlas Search Text and Phrase operators (#1678)
1 parent eff1f25 commit d6840c3

File tree

7 files changed

+280
-17
lines changed

7 files changed

+280
-17
lines changed
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
namespace MongoDB.Driver.Search
17+
{
18+
/// <summary>
19+
/// Represents the criteria used to match terms in a query for the Atlas Search Text operator.
20+
/// </summary>
21+
public enum MatchCriteria
22+
{
23+
/// <summary>
24+
/// Match documents containing any of the terms from a query.
25+
/// </summary>
26+
Any,
27+
/// <summary>
28+
/// Match documents containing all the terms from a query.
29+
/// </summary>
30+
All
31+
}
32+
}

src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs

+27-14
Original file line numberDiff line numberDiff line change
@@ -328,24 +328,26 @@ internal sealed class PhraseSearchDefinition<TDocument> : OperatorSearchDefiniti
328328
{
329329
private readonly SearchQueryDefinition _query;
330330
private readonly int? _slop;
331+
private readonly string _synonyms;
331332

332333
public PhraseSearchDefinition(
333334
SearchPathDefinition<TDocument> path,
334335
SearchQueryDefinition query,
335-
int? slop,
336-
SearchScoreDefinition<TDocument> score)
337-
: base(OperatorType.Phrase, path, score)
336+
SearchPhraseOptions<TDocument> options)
337+
: base(OperatorType.Phrase, path, options?.Score)
338338
{
339339
_query = Ensure.IsNotNull(query, nameof(query));
340-
_slop = slop;
340+
_slop = options?.Slop;
341+
_synonyms = options?.Synonyms;
341342
}
342343

343344
private protected override BsonDocument RenderArguments(
344345
RenderArgs<TDocument> args,
345346
IBsonSerializer fieldSerializer) => new()
346347
{
347348
{ "query", _query.Render() },
348-
{ "slop", _slop, _slop != null }
349+
{ "slop", _slop, _slop != null },
350+
{ "synonyms", _synonyms, _synonyms != null }
349351
};
350352
}
351353

@@ -461,29 +463,40 @@ private protected override BsonDocument RenderArguments(
461463
internal sealed class TextSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>
462464
{
463465
private readonly SearchFuzzyOptions _fuzzy;
466+
private readonly string _matchCriteria;
464467
private readonly SearchQueryDefinition _query;
465468
private readonly string _synonyms;
466469

467470
public TextSearchDefinition(
468471
SearchPathDefinition<TDocument> path,
469472
SearchQueryDefinition query,
470-
SearchFuzzyOptions fuzzy,
471-
SearchScoreDefinition<TDocument> score,
472-
string synonyms)
473-
: base(OperatorType.Text, path, score)
473+
SearchTextOptions<TDocument> options)
474+
: base(OperatorType.Text, path, options?.Score)
474475
{
475476
_query = Ensure.IsNotNull(query, nameof(query));
476-
_fuzzy = fuzzy;
477-
_synonyms = synonyms;
477+
_fuzzy = options?.Fuzzy;
478+
_synonyms = options?.Synonyms;
479+
_matchCriteria = options?.MatchCriteria switch
480+
{
481+
MatchCriteria.All => "all",
482+
MatchCriteria.Any => "any",
483+
null => null,
484+
_ => throw new ArgumentException("Invalid match criteria set for Atlas Search text operator.")
485+
};
478486
}
479487

480-
private protected override BsonDocument RenderArguments(RenderArgs<TDocument> args,
481-
IBsonSerializer fieldSerializer) => new()
488+
private protected override BsonDocument RenderArguments(
489+
RenderArgs<TDocument> args,
490+
IBsonSerializer fieldSerializer)
491+
{
492+
return new BsonDocument
482493
{
483494
{ "query", _query.Render() },
484495
{ "fuzzy", () => _fuzzy.Render(), _fuzzy != null },
485-
{ "synonyms", _synonyms, _synonyms != null }
496+
{ "synonyms", _synonyms, _synonyms != null },
497+
{ "matchCriteria", _matchCriteria, _matchCriteria != null }
486498
};
499+
}
487500
}
488501

489502
internal sealed class WildcardSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>

src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs

+61-3
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,21 @@ public SearchDefinition<TDocument> Phrase(
550550
SearchQueryDefinition query,
551551
int? slop = null,
552552
SearchScoreDefinition<TDocument> score = null) =>
553-
new PhraseSearchDefinition<TDocument>(path, query, slop, score);
553+
new PhraseSearchDefinition<TDocument>(path, query, new SearchPhraseOptions<TDocument> { Slop = slop, Score = score });
554+
555+
/// <summary>
556+
/// Creates a search definition that performs search for documents containing an ordered
557+
/// sequence of terms.
558+
/// </summary>
559+
/// <param name="path">The indexed field or fields to search.</param>
560+
/// <param name="query">The string or strings to search for.</param>
561+
/// <param name="options">The options.</param>
562+
/// <returns>A phrase search definition.</returns>
563+
public SearchDefinition<TDocument> Phrase(
564+
SearchPathDefinition<TDocument> path,
565+
SearchQueryDefinition query,
566+
SearchPhraseOptions<TDocument> options) =>
567+
new PhraseSearchDefinition<TDocument>(path, query, options);
554568

555569
/// <summary>
556570
/// Creates a search definition that performs search for documents containing an ordered
@@ -569,6 +583,21 @@ public SearchDefinition<TDocument> Phrase<TField>(
569583
SearchScoreDefinition<TDocument> score = null) =>
570584
Phrase(new ExpressionFieldDefinition<TDocument>(path), query, slop, score);
571585

586+
/// <summary>
587+
/// Creates a search definition that performs search for documents containing an ordered
588+
/// sequence of terms.
589+
/// </summary>
590+
/// <typeparam name="TField">The type of the field.</typeparam>
591+
/// <param name="path">The indexed field or fields to search.</param>
592+
/// <param name="query">The string or strings to search for.</param>
593+
/// <param name="options">The options.</param>
594+
/// <returns>A phrase search definition.</returns>
595+
public SearchDefinition<TDocument> Phrase<TField>(
596+
Expression<Func<TDocument, TField>> path,
597+
SearchQueryDefinition query,
598+
SearchPhraseOptions<TDocument> options) =>
599+
Phrase(new ExpressionFieldDefinition<TDocument>(path), query, options);
600+
572601
/// <summary>
573602
/// Creates a search definition that queries a combination of indexed fields and values.
574603
/// </summary>
@@ -732,6 +761,20 @@ public SearchDefinition<TDocument> Regex<TField>(
732761
public SearchDefinition<TDocument> Span(SearchSpanDefinition<TDocument> clause) =>
733762
new SpanSearchDefinition<TDocument>(clause);
734763

764+
/// <summary>
765+
/// Creates a search definition that performs full-text search using the analyzer specified
766+
/// in the index configuration.
767+
/// </summary>
768+
/// <param name="path">The indexed field or fields to search.</param>
769+
/// <param name="query">The string or strings to search for.</param>
770+
/// <param name="options">The options.</param>
771+
/// <returns>A text search definition.</returns>
772+
public SearchDefinition<TDocument> Text(
773+
SearchPathDefinition<TDocument> path,
774+
SearchQueryDefinition query,
775+
SearchTextOptions<TDocument> options) =>
776+
new TextSearchDefinition<TDocument>(path, query, options);
777+
735778
/// <summary>
736779
/// Creates a search definition that performs full-text search using the analyzer specified
737780
/// in the index configuration.
@@ -746,7 +789,7 @@ public SearchDefinition<TDocument> Text(
746789
SearchQueryDefinition query,
747790
SearchFuzzyOptions fuzzy = null,
748791
SearchScoreDefinition<TDocument> score = null) =>
749-
new TextSearchDefinition<TDocument>(path, query, fuzzy, score, null);
792+
new TextSearchDefinition<TDocument>(path, query, new SearchTextOptions<TDocument> { Fuzzy = fuzzy, Score = score });
750793

751794
/// <summary>
752795
/// Creates a search definition that performs full-text search with synonyms using the analyzer specified
@@ -762,7 +805,22 @@ public SearchDefinition<TDocument> Text(
762805
SearchQueryDefinition query,
763806
string synonyms,
764807
SearchScoreDefinition<TDocument> score = null) =>
765-
new TextSearchDefinition<TDocument>(path, query, null, score, synonyms);
808+
new TextSearchDefinition<TDocument>(path, query, new SearchTextOptions<TDocument> { Score = score, Synonyms = synonyms });
809+
810+
/// <summary>
811+
/// Creates a search definition that performs full-text search using the analyzer specified
812+
/// in the index configuration.
813+
/// </summary>
814+
/// <typeparam name="TField">The type of the field.</typeparam>
815+
/// <param name="path">The indexed field or field to search.</param>
816+
/// <param name="query">The string or strings to search for.</param>
817+
/// <param name="options">The options.</param>
818+
/// <returns>A text search definition.</returns>
819+
public SearchDefinition<TDocument> Text<TField>(
820+
Expression<Func<TDocument, TField>> path,
821+
SearchQueryDefinition query,
822+
SearchTextOptions<TDocument> options) =>
823+
Text(new ExpressionFieldDefinition<TDocument>(path), query, options);
766824

767825
/// <summary>
768826
/// Creates a search definition that performs full-text search using the analyzer specified
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
namespace MongoDB.Driver.Search
17+
{
18+
/// <summary>
19+
/// Options for atlas search phrase operator.
20+
/// </summary>
21+
public sealed class SearchPhraseOptions<TDocument>
22+
{
23+
/// <summary>
24+
/// The score modifier.
25+
/// </summary>
26+
public SearchScoreDefinition<TDocument> Score { get; set; }
27+
28+
/// <summary>
29+
/// The allowable distance between words in the query phrase.
30+
/// </summary>
31+
public int? Slop { get; set; }
32+
33+
/// <summary>
34+
/// The name of the synonym mapping definition in the index definition. Value can't be an empty string (e.g. "").
35+
/// </summary>
36+
public string Synonyms { get; set; }
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
namespace MongoDB.Driver.Search
17+
{
18+
/// <summary>
19+
/// Options for atlas search text operator.
20+
/// </summary>
21+
public sealed class SearchTextOptions<TDocument>
22+
{
23+
/// <summary>
24+
/// The options for fuzzy search.
25+
/// </summary>
26+
public SearchFuzzyOptions Fuzzy { get; set; }
27+
28+
/// <summary>
29+
/// The criteria to use to match the terms in the query. Value can be either "any" or "all".
30+
/// Defaults to "all" if omitted.
31+
/// </summary>
32+
public MatchCriteria? MatchCriteria { get; set; }
33+
34+
/// <summary>
35+
/// The score modifier.
36+
/// </summary>
37+
public SearchScoreDefinition<TDocument> Score { get; set; }
38+
39+
/// <summary>
40+
/// The name of the synonym mapping definition in the index definition. Value can't be an empty string (e.g. "").
41+
/// </summary>
42+
public string Synonyms { get; set; }
43+
}
44+
}

tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs

+40
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,26 @@ public void PhraseAnalyzerPath()
421421
result.Title.Should().Be("Declaration of Independence");
422422
}
423423

424+
[Fact]
425+
public void PhraseSynonym()
426+
{
427+
var result =
428+
GetSynonymTestCollection().Aggregate()
429+
.Search(
430+
Builders<Movie>.Search.Phrase("plot", "automobile race", new SearchPhraseOptions<Movie> { Synonyms = "transportSynonyms" }),
431+
indexName: "synonyms-tests")
432+
.Project<Movie>(Builders<Movie>.Projection.Include("Title").Exclude("_id"))
433+
.Limit(5)
434+
.ToList();
435+
436+
result.Count.Should().Be(5);
437+
result[0].Title.Should().Be("The Great Race");
438+
result[1].Title.Should().Be("The Cannonball Run");
439+
result[2].Title.Should().Be("National Mechanics");
440+
result[3].Title.Should().Be("Genevieve");
441+
result[4].Title.Should().Be("Speedway Junky");
442+
}
443+
424444
[Fact]
425445
public void PhraseWildcardPath()
426446
{
@@ -723,6 +743,26 @@ public void Text()
723743
result.Title.Should().Be("Declaration of Independence");
724744
}
725745

746+
[Fact]
747+
public void TextMatchCriteria()
748+
{
749+
var result =
750+
GetSynonymTestCollection().Aggregate()
751+
.Search(
752+
Builders<Movie>.Search.Text("plot", "attire", new SearchTextOptions<Movie> { Synonyms = "attireSynonyms", MatchCriteria = MatchCriteria.Any}),
753+
indexName: "synonyms-tests")
754+
.Project<Movie>(Builders<Movie>.Projection.Include("Title").Exclude("_id"))
755+
.Limit(5)
756+
.ToList();
757+
758+
result.Count.Should().Be(5);
759+
result[0].Title.Should().Be("The Royal Tailor");
760+
result[1].Title.Should().Be("La guerre des tuques");
761+
result[2].Title.Should().Be("The Dress");
762+
result[3].Title.Should().Be("The Club");
763+
result[4].Title.Should().Be("The Triple Echo");
764+
}
765+
726766
[Theory]
727767
[InlineData("automobile", "transportSynonyms", "Blue Car")]
728768
[InlineData("boat", "transportSynonyms", "And the Ship Sails On")]

0 commit comments

Comments
 (0)