diff --git a/src/MongoDB.Driver/AggregateFluent.cs b/src/MongoDB.Driver/AggregateFluent.cs index f4732d3fc69..52db3ab89ef 100644 --- a/src/MongoDB.Driver/AggregateFluent.cs +++ b/src/MongoDB.Driver/AggregateFluent.cs @@ -244,9 +244,10 @@ public override IAggregateFluent Search( SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false) + bool returnStoredSource = false, + bool scoreDetails = false) { - return WithPipeline(_pipeline.Search(searchDefinition, highlight, indexName, count, returnStoredSource)); + return WithPipeline(_pipeline.Search(searchDefinition, highlight, indexName, count, returnStoredSource, scoreDetails)); } public override IAggregateFluent SearchMeta( diff --git a/src/MongoDB.Driver/AggregateFluentBase.cs b/src/MongoDB.Driver/AggregateFluentBase.cs index 8239750962a..511eced50ec 100644 --- a/src/MongoDB.Driver/AggregateFluentBase.cs +++ b/src/MongoDB.Driver/AggregateFluentBase.cs @@ -222,7 +222,8 @@ public virtual IAggregateFluent Search( SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false) + bool returnStoredSource = false, + bool scoreDetails = false) { throw new NotImplementedException(); } diff --git a/src/MongoDB.Driver/IAggregateFluent.cs b/src/MongoDB.Driver/IAggregateFluent.cs index cca33c9e1c9..079f16d897d 100644 --- a/src/MongoDB.Driver/IAggregateFluent.cs +++ b/src/MongoDB.Driver/IAggregateFluent.cs @@ -365,13 +365,18 @@ IAggregateFluent SetWindowFields( /// Flag that specifies whether to perform a full document lookup on the backend database /// or return only stored source fields directly from Atlas Search. /// + /// + /// Flag that specifies whether to return a detailed breakdown + /// of the score for each document in the result. + /// /// The fluent aggregate interface. IAggregateFluent Search( SearchDefinition searchDefinition, SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false); + bool returnStoredSource = false, + bool scoreDetails = false); /// /// Appends a $searchMeta stage to the pipeline. diff --git a/src/MongoDB.Driver/Linq/MongoQueryable.cs b/src/MongoDB.Driver/Linq/MongoQueryable.cs index b84eba14b88..ffee7be89d3 100644 --- a/src/MongoDB.Driver/Linq/MongoQueryable.cs +++ b/src/MongoDB.Driver/Linq/MongoQueryable.cs @@ -1149,6 +1149,10 @@ public static IMongoQueryable Sample(this IMongoQueryable + /// + /// Flag that specifies whether to return a detailed breakdown + /// of the score for each document in the result. + /// /// The queryable with a new stage appended. public static IMongoQueryable Search( this IMongoQueryable source, @@ -1156,11 +1160,12 @@ public static IMongoQueryable Search( SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false) + bool returnStoredSource = false, + bool scoreDetails = false) { return AppendStage( source, - PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource)); + PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource, scoreDetails)); } /// diff --git a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs index a78b3f98af1..8c25fe5c2e3 100644 --- a/src/MongoDB.Driver/PipelineDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineDefinitionBuilder.cs @@ -1179,6 +1179,10 @@ public static PipelineDefinition ReplaceWith + /// + /// Flag that specifies whether to return a detailed breakdown + /// of the score for each document in the result. + /// /// /// A new pipeline with an additional stage. /// @@ -1188,10 +1192,11 @@ public static PipelineDefinition Search( SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false) + bool returnStoredSource = false, + bool scoreDetails = false) { Ensure.IsNotNull(pipeline, nameof(pipeline)); - return pipeline.AppendStage(PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource)); + return pipeline.AppendStage(PipelineStageDefinitionBuilder.Search(searchDefinition, highlight, indexName, count, returnStoredSource, scoreDetails)); } /// diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs index 636345d6413..6ef9e68c111 100644 --- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs @@ -1320,13 +1320,18 @@ public static PipelineStageDefinition Project( /// Flag that specifies whether to perform a full document lookup on the backend database /// or return only stored source fields directly from Atlas Search. /// + /// + /// Flag that specifies whether to return a detailed breakdown + /// of the score for each document in the result. + /// /// The stage. public static PipelineStageDefinition Search( SearchDefinition searchDefinition, SearchHighlightOptions highlight = null, string indexName = null, SearchCountOptions count = null, - bool returnStoredSource = false) + bool returnStoredSource = false, + bool scoreDetails = false) { Ensure.IsNotNull(searchDefinition, nameof(searchDefinition)); @@ -1340,6 +1345,7 @@ public static PipelineStageDefinition Search( renderedSearchDefinition.Add("count", () => count.Render(), count != null); renderedSearchDefinition.Add("index", indexName, indexName != null); renderedSearchDefinition.Add("returnStoredSource", returnStoredSource, returnStoredSource); + renderedSearchDefinition.Add("scoreDetails", scoreDetails, scoreDetails); var document = new BsonDocument(operatorName, renderedSearchDefinition); return new RenderedPipelineStageDefinition(operatorName, document, s); diff --git a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs index 52b01102815..8fdd6d140e5 100644 --- a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs +++ b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs @@ -190,6 +190,23 @@ public static ProjectionDefinition MetaSearchScore( return builder.Combine(projection, builder.MetaSearchScore(field)); } + /// + /// Combines an existing projection with a search score details projection. + /// + /// The type of the document. + /// The projection. + /// The field. + /// + /// A combined projection. + /// + public static ProjectionDefinition MetaSearchScoreDetails( + this ProjectionDefinition projection, + string field) + { + var builder = Builders.Projection; + return builder.Combine(projection, builder.MetaSearchScoreDetails(field)); + } + /// /// Combines an existing projection with a text score projection. /// @@ -455,6 +472,18 @@ public ProjectionDefinition MetaSearchScore(string field) return Meta(field, "searchScore"); } + /// + /// Creates a search score details projection. + /// + /// The field. + /// + /// A search score details projection. + /// + public ProjectionDefinition MetaSearchScoreDetails(string field) + { + return Meta(field, "searchScoreDetails"); + } + /// /// Creates a text score projection. /// diff --git a/src/MongoDB.Driver/Search/SearchScoreDetails.cs b/src/MongoDB.Driver/Search/SearchScoreDetails.cs new file mode 100644 index 00000000000..897a9b0143f --- /dev/null +++ b/src/MongoDB.Driver/Search/SearchScoreDetails.cs @@ -0,0 +1,60 @@ +/* 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. +*/ + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace MongoDB.Driver.Search +{ + /// + /// Represents the scoreDetails object for a document in the result. + /// + public sealed class SearchScoreDetails + { + /// + /// Initializes a new instance of the class. + /// + /// Contribution towards the score by a subset of the scoring formula. + /// Subset of the scoring formula. + /// Breakdown of the score for each match in the document. + public SearchScoreDetails(double value, string description, SearchScoreDetails[] details) + { + Value = value; + Description = description; + Details = details; + } + + /// + /// Gets the contribution towards the score by a subset of the scoring formula. + /// + [BsonElement("value")] + public double Value { get; } + + /// + /// Gets the subset of the scoring formula including details about how the document + /// was scored and factors considered in calculating the score. + /// + [BsonElement("description")] + public string Description { get; } + + /// + /// Breakdown of the score for each match in the document based on the subset of the scoring formula. + /// (if any). + /// + [BsonDefaultValue(null)] + [BsonElement("details")] + public SearchScoreDetails[] Details { get; } + } +} diff --git a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs index a393565a60d..384f28dc28b 100644 --- a/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/PipelineDefinitionBuilderTests.cs @@ -182,6 +182,18 @@ public void Search_should_add_expected_stage_with_return_stored_source() stages[0].Should().Be("{ $search: { text: { query: 'foo', path: 'bar' }, returnStoredSource: true } }"); } + [Fact] + public void Search_should_add_expected_stage_with_score_details() + { + var pipeline = new EmptyPipelineDefinition(); + var builder = new SearchDefinitionBuilder(); + + var result = pipeline.Search(builder.Text("bar", "foo"), scoreDetails: true); + + var stages = RenderStages(result, BsonDocumentSerializer.Instance); + stages[0].Should().Be("{ $search: { text: { query: 'foo', path: 'bar' }, scoreDetails: true } }"); + } + [Fact] public void Search_should_throw_when_pipeline_is_null() { diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs index cb36c5d6482..dd8a52ec5a7 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs @@ -256,13 +256,15 @@ public void Phrase() .Search(Builders.Search.Phrase(x => x.Body, "life, liberty, and the pursuit of happiness"), new SearchHighlightOptions(x => x.Body), indexName: "default", - returnStoredSource: true) + returnStoredSource: true, + scoreDetails: true) .Limit(1) .Project(Builders.Projection .Include(x => x.Title) .Include(x => x.Body) .MetaSearchScore("score") - .MetaSearchHighlights("highlights")) + .MetaSearchHighlights("highlights") + .MetaSearchScoreDetails("scoreDetails")) .ToList(); var result = results.Should().ContainSingle().Subject; @@ -280,6 +282,14 @@ public void Phrase() var highlightRangeStr = string.Join(string.Empty, highlightTexts.Skip(1).Select(x => x.Value)); highlightRangeStr.Should().Be("Life, Liberty and the pursuit of Happiness."); + + result.ScoreDetails.Description.Should().Contain("life liberty and the pursuit of happiness"); + result.ScoreDetails.Value.Should().NotBe(0); + + var scoreDetail = result.ScoreDetails.Details.Should().ContainSingle().Subject; + scoreDetail.Description.Should().NotBeNullOrEmpty(); + scoreDetail.Value.Should().NotBe(0); + scoreDetail.Details.Should().NotBeEmpty(); } [Fact] @@ -493,6 +503,9 @@ public class HistoricalDocument [BsonElement("metaResult")] public SearchMetaResult MetaResult { get; set; } + + [BsonElement("scoreDetails")] + public SearchScoreDetails ScoreDetails { get; set; } } [BsonIgnoreExtraElements] diff --git a/tests/MongoDB.Driver.Tests/Search/ProjectionDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/ProjectionDefinitionBuilderTests.cs index d827ca1220f..20933df595f 100644 --- a/tests/MongoDB.Driver.Tests/Search/ProjectionDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/ProjectionDefinitionBuilderTests.cs @@ -38,6 +38,14 @@ public void MetaSearchScore() AssertRendered(subject.MetaSearchScore("a"), "{ a : { $meta: 'searchScore' } }"); } + [Fact] + public void MetaSearchScoreDetails() + { + var subject = CreateSubject(); + + AssertRendered(subject.MetaSearchScoreDetails("a"), "{ a : { $meta: 'searchScoreDetails' } }"); + } + [Fact] public void SearchMeta() {