diff options
author | Mihai Andrei <mihai.andrei@10gen.com> | 2020-11-10 09:39:11 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-13 22:41:49 +0000 |
commit | 6ef44c9d2db276e2bd7f5a49dea9324be3d5fd02 (patch) | |
tree | d3ef830319eeccd6186f32eb1782709ae0ce54fd | |
parent | c38ec5727899cd563791d5ea4ec054cf6322498c (diff) | |
download | mongo-6ef44c9d2db276e2bd7f5a49dea9324be3d5fd02.tar.gz |
SERVER-49744 Per-document scoring metadata for $search
11 files changed, 168 insertions, 16 deletions
diff --git a/src/mongo/db/exec/document_value/document.cpp b/src/mongo/db/exec/document_value/document.cpp index a95c5adbf82..f9c37fb91ac 100644 --- a/src/mongo/db/exec/document_value/document.cpp +++ b/src/mongo/db/exec/document_value/document.cpp @@ -53,7 +53,8 @@ const StringDataSet Document::allMetadataFieldNames{Document::metaFieldTextScore Document::metaFieldGeoNearPoint, Document::metaFieldSearchScore, Document::metaFieldSearchHighlights, - Document::metaFieldIndexKey}; + Document::metaFieldIndexKey, + Document::metaFieldSearchScoreDetails}; DocumentStorageIterator::DocumentStorageIterator(DocumentStorage* storage, BSONObjIterator bsonIt) : _bsonIt(std::move(bsonIt)), @@ -398,6 +399,8 @@ void DocumentStorage::loadLazyMetadata() const { _metadataFields.setGeoNearPoint(val); } else if (fieldName == Document::metaFieldIndexKey) { _metadataFields.setIndexKey(elem.Obj()); + } else if (fieldName == Document::metaFieldSearchScoreDetails) { + _metadataFields.setSearchScoreDetails(elem.Obj()); } } } @@ -476,6 +479,7 @@ constexpr StringData Document::metaFieldGeoNearDistance; constexpr StringData Document::metaFieldGeoNearPoint; constexpr StringData Document::metaFieldSearchScore; constexpr StringData Document::metaFieldSearchHighlights; +constexpr StringData Document::metaFieldSearchScoreDetails; BSONObj Document::toBsonWithMetaData() const { BSONObjBuilder bb; @@ -502,6 +506,8 @@ BSONObj Document::toBsonWithMetaData() const { metadata().getSearchHighlights().addToBsonObj(&bb, metaFieldSearchHighlights); if (metadata().hasIndexKey()) bb.append(metaFieldIndexKey, metadata().getIndexKey()); + if (metadata().hasSearchScoreDetails()) + bb.append(metaFieldSearchScoreDetails, metadata().getSearchScoreDetails()); return bb.obj(); } diff --git a/src/mongo/db/exec/document_value/document.h b/src/mongo/db/exec/document_value/document.h index 27b3c506fd4..439c88f1cc2 100644 --- a/src/mongo/db/exec/document_value/document.h +++ b/src/mongo/db/exec/document_value/document.h @@ -99,6 +99,7 @@ public: static constexpr StringData metaFieldGeoNearPoint = "$pt"_sd; static constexpr StringData metaFieldSearchScore = "$searchScore"_sd; static constexpr StringData metaFieldSearchHighlights = "$searchHighlights"_sd; + static constexpr StringData metaFieldSearchScoreDetails = "$searchScoreDetails"_sd; static constexpr StringData metaFieldIndexKey = "$indexKey"_sd; static const StringDataSet allMetadataFieldNames; diff --git a/src/mongo/db/exec/document_value/document_metadata_fields.cpp b/src/mongo/db/exec/document_value/document_metadata_fields.cpp index 6d58f4ce7f4..aad877e8308 100644 --- a/src/mongo/db/exec/document_value/document_metadata_fields.cpp +++ b/src/mongo/db/exec/document_value/document_metadata_fields.cpp @@ -82,6 +82,9 @@ void DocumentMetadataFields::mergeWith(const DocumentMetadataFields& other) { if (!hasIndexKey() && other.hasIndexKey()) { setIndexKey(other.getIndexKey()); } + if (!hasSearchScoreDetails() && other.hasSearchScoreDetails()) { + setSearchScoreDetails(other.getSearchScoreDetails()); + } } void DocumentMetadataFields::copyFrom(const DocumentMetadataFields& other) { @@ -109,6 +112,9 @@ void DocumentMetadataFields::copyFrom(const DocumentMetadataFields& other) { if (other.hasIndexKey()) { setIndexKey(other.getIndexKey()); } + if (other.hasSearchScoreDetails()) { + setSearchScoreDetails(other.getSearchScoreDetails()); + } } size_t DocumentMetadataFields::getApproximateSize() const { @@ -129,6 +135,7 @@ size_t DocumentMetadataFields::getApproximateSize() const { size += _holder->searchHighlights.getApproximateSize(); size -= sizeof(_holder->searchHighlights); size += _holder->indexKey.objsize(); + size += _holder->searchScoreDetails.objsize(); return size; } @@ -173,6 +180,10 @@ void DocumentMetadataFields::serializeForSorter(BufBuilder& buf) const { buf.appendNum(static_cast<char>(MetaType::kIndexKey + 1)); getIndexKey().appendSelfToBufBuilder(buf); } + if (hasSearchScoreDetails()) { + buf.appendNum(static_cast<char>(MetaType::kSearchScoreDetails + 1)); + getSearchScoreDetails().appendSelfToBufBuilder(buf); + } buf.appendNum(static_cast<char>(0)); } @@ -201,6 +212,9 @@ void DocumentMetadataFields::deserializeForSorter(BufReader& buf, DocumentMetada } else if (marker == static_cast<char>(MetaType::kIndexKey) + 1) { out->setIndexKey( BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings())); + } else if (marker == static_cast<char>(MetaType::kSearchScoreDetails) + 1) { + out->setSearchScoreDetails( + BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings())); } else { uasserted(28744, "Unrecognized marker, unable to deserialize buffer"); } @@ -254,6 +268,8 @@ const char* DocumentMetadataFields::typeNameToDebugString(DocumentMetadataFields return "sort key"; case DocumentMetadataFields::kTextScore: return "text score"; + case DocumentMetadataFields::kSearchScoreDetails: + return "$search score details"; default: MONGO_UNREACHABLE; } diff --git a/src/mongo/db/exec/document_value/document_metadata_fields.h b/src/mongo/db/exec/document_value/document_metadata_fields.h index 71497516716..281d77ae671 100644 --- a/src/mongo/db/exec/document_value/document_metadata_fields.h +++ b/src/mongo/db/exec/document_value/document_metadata_fields.h @@ -64,6 +64,7 @@ public: kSearchScore, kSortKey, kTextScore, + kSearchScoreDetails, // New fields must be added before the kNumFields sentinel. kNumFields @@ -302,6 +303,23 @@ public: _holder->recordId = rid; } + bool hasSearchScoreDetails() const { + return _holder && _holder->metaFields.test(MetaType::kSearchScoreDetails); + } + + BSONObj getSearchScoreDetails() const { + invariant(hasSearchScoreDetails()); + return _holder->searchScoreDetails; + } + + void setSearchScoreDetails(BSONObj details) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + _holder->metaFields.set(MetaType::kSearchScoreDetails); + _holder->searchScoreDetails = details.getOwned(); + } + void serializeForSorter(BufBuilder& buf) const; private: @@ -323,6 +341,7 @@ private: Value searchHighlights; BSONObj indexKey; RecordId recordId; + BSONObj searchScoreDetails; }; // Null until the first setter is called, at which point a MetadataHolder struct is allocated. diff --git a/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp b/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp index b783406ce29..5515009b2bd 100644 --- a/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp +++ b/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp @@ -48,6 +48,8 @@ TEST(DocumentMetadataFieldsTest, AllMetadataRoundtripsThroughSerialization) { metadata.setSearchScore(5.4); metadata.setSearchHighlights(Value{"foo"_sd}); metadata.setIndexKey(BSON("b" << 1)); + metadata.setSearchScoreDetails(BSON("scoreDetails" + << "foo")); BufBuilder builder; metadata.serializeForSorter(builder); @@ -64,6 +66,9 @@ TEST(DocumentMetadataFieldsTest, AllMetadataRoundtripsThroughSerialization) { ASSERT_EQ(deserialized.getSearchScore(), 5.4); ASSERT_VALUE_EQ(deserialized.getSearchHighlights(), Value{"foo"_sd}); ASSERT_BSONOBJ_EQ(deserialized.getIndexKey(), BSON("b" << 1)); + ASSERT_BSONOBJ_EQ(deserialized.getSearchScoreDetails(), + BSON("scoreDetails" + << "foo")); } TEST(DocumentMetadataFieldsTest, HasMethodsReturnFalseForEmptyMetadata) { @@ -77,6 +82,7 @@ TEST(DocumentMetadataFieldsTest, HasMethodsReturnFalseForEmptyMetadata) { ASSERT_FALSE(metadata.hasSearchScore()); ASSERT_FALSE(metadata.hasSearchHighlights()); ASSERT_FALSE(metadata.hasIndexKey()); + ASSERT_FALSE(metadata.hasSearchScoreDetails()); } TEST(DocumentMetadataFieldsTest, HasMethodsReturnTrueForInitializedMetadata) { @@ -114,6 +120,11 @@ TEST(DocumentMetadataFieldsTest, HasMethodsReturnTrueForInitializedMetadata) { ASSERT_FALSE(metadata.hasIndexKey()); metadata.setIndexKey(BSON("b" << 1)); ASSERT_TRUE(metadata.hasIndexKey()); + + ASSERT_FALSE(metadata.hasSearchScoreDetails()); + metadata.setSearchScoreDetails(BSON("scoreDetails" + << "foo")); + ASSERT_TRUE(metadata.hasSearchScoreDetails()); } TEST(DocumentMetadataFieldsTest, MoveConstructor) { @@ -126,6 +137,8 @@ TEST(DocumentMetadataFieldsTest, MoveConstructor) { metadata.setSearchScore(5.4); metadata.setSearchHighlights(Value{"foo"_sd}); metadata.setIndexKey(BSON("b" << 1)); + metadata.setSearchScoreDetails(BSON("scoreDetails" + << "foo")); DocumentMetadataFields moveConstructed(std::move(metadata)); ASSERT_TRUE(moveConstructed); @@ -138,6 +151,9 @@ TEST(DocumentMetadataFieldsTest, MoveConstructor) { ASSERT_EQ(moveConstructed.getSearchScore(), 5.4); ASSERT_VALUE_EQ(moveConstructed.getSearchHighlights(), Value{"foo"_sd}); ASSERT_BSONOBJ_EQ(moveConstructed.getIndexKey(), BSON("b" << 1)); + ASSERT_BSONOBJ_EQ(moveConstructed.getSearchScoreDetails(), + BSON("scoreDetails" + << "foo")); ASSERT_FALSE(metadata); // NOLINT(bugprone-use-after-move) } @@ -152,6 +168,8 @@ TEST(DocumentMetadataFieldsTest, MoveAssignmentOperator) { metadata.setSearchScore(5.4); metadata.setSearchHighlights(Value{"foo"_sd}); metadata.setIndexKey(BSON("b" << 1)); + metadata.setSearchScoreDetails(BSON("scoreDetails" + << "foo")); DocumentMetadataFields moveAssigned; moveAssigned.setTextScore(12.3); @@ -167,6 +185,9 @@ TEST(DocumentMetadataFieldsTest, MoveAssignmentOperator) { ASSERT_EQ(moveAssigned.getSearchScore(), 5.4); ASSERT_VALUE_EQ(moveAssigned.getSearchHighlights(), Value{"foo"_sd}); ASSERT_BSONOBJ_EQ(moveAssigned.getIndexKey(), BSON("b" << 1)); + ASSERT_BSONOBJ_EQ(moveAssigned.getSearchScoreDetails(), + BSON("scoreDetails" + << "foo")); ASSERT_FALSE(metadata); // NOLINT(bugprone-use-after-move) } @@ -219,6 +240,7 @@ TEST(DocumentMetadataFieldsTest, MergeWithOnlyCopiesMetadataThatDestinationDoesN ASSERT_FALSE(destination.hasSearchScore()); ASSERT_FALSE(destination.hasSearchHighlights()); ASSERT_FALSE(destination.hasIndexKey()); + ASSERT_FALSE(destination.hasSearchScoreDetails()); } TEST(DocumentMetadataFieldsTest, CopyFromCopiesAllMetadataThatSourceHas) { @@ -243,6 +265,7 @@ TEST(DocumentMetadataFieldsTest, CopyFromCopiesAllMetadataThatSourceHas) { ASSERT_FALSE(destination.hasSearchScore()); ASSERT_FALSE(destination.hasSearchHighlights()); ASSERT_FALSE(destination.hasIndexKey()); + ASSERT_FALSE(destination.hasSearchScoreDetails()); } } // namespace mongo diff --git a/src/mongo/db/exec/document_value/document_value_test.cpp b/src/mongo/db/exec/document_value/document_value_test.cpp index 71bf3b814a3..6ca845670a3 100644 --- a/src/mongo/db/exec/document_value/document_value_test.cpp +++ b/src/mongo/db/exec/document_value/document_value_test.cpp @@ -769,6 +769,30 @@ TEST(MetaFields, SearchHighlightsBasic) { ASSERT_VALUE_EQ(doc2.metadata().getSearchHighlights(), otherHighlights); } +TEST(MetaFields, SearchScoreDetailsBasic) { + // Documents should not have a value for searchScoreDetails until it is set. + ASSERT_FALSE(Document().metadata().hasSearchScoreDetails()); + + // Setting the searchScoreDetails field should work as expected. + MutableDocument docBuilder; + BSONObj details = BSON("scoreDetails" + << "foo"); + docBuilder.metadata().setSearchScoreDetails(details); + Document doc = docBuilder.freeze(); + ASSERT_TRUE(doc.metadata().hasSearchScoreDetails()); + ASSERT_BSONOBJ_EQ(doc.metadata().getSearchScoreDetails(), details); + + // Setting the searchScoreDetails twice should keep the second value. + MutableDocument docBuilder2; + BSONObj otherDetails = BSON("scoreDetails" + << "bar"); + docBuilder2.metadata().setSearchScoreDetails(details); + docBuilder2.metadata().setSearchScoreDetails(otherDetails); + Document doc2 = docBuilder2.freeze(); + ASSERT_TRUE(doc2.metadata().hasSearchScoreDetails()); + ASSERT_BSONOBJ_EQ(doc2.metadata().getSearchScoreDetails(), otherDetails); +} + TEST(MetaFields, IndexKeyMetadataSerializesCorrectly) { Document doc{BSON("a" << 1)}; MutableDocument mutableDoc{doc}; @@ -797,7 +821,9 @@ TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) { << BSON_ARRAY(1 << 2) << "f" << 1 << "$searchScore" << 5.4 << "g" << 1 << "$searchHighlights" << "foo" - << "h" << 1 << "$indexKey" << BSON("y" << 1))); + << "h" << 1 << "$indexKey" << BSON("y" << 1) << "$searchScoreDetails" + << BSON("scoreDetails" + << "foo"))); MutableDocument destination{}; destination.copyMetaDataFrom(source); @@ -811,6 +837,9 @@ TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) { ASSERT_EQ(result.metadata().getSearchScore(), 5.4); ASSERT_VALUE_EQ(result.metadata().getSearchHighlights(), Value{"foo"_sd}); ASSERT_BSONOBJ_EQ(result.metadata().getIndexKey(), BSON("y" << 1)); + ASSERT_BSONOBJ_EQ(result.metadata().getSearchScoreDetails(), + BSON("scoreDetails" + << "foo")); } class SerializationTest : public unittest::Test { @@ -847,6 +876,10 @@ protected: if (input.metadata().hasIndexKey()) { ASSERT_BSONOBJ_EQ(output.metadata().getIndexKey(), input.metadata().getIndexKey()); } + if (input.metadata().hasSearchScoreDetails()) { + ASSERT_BSONOBJ_EQ(output.metadata().getSearchScoreDetails(), + input.metadata().getSearchScoreDetails()); + } ASSERT(output.toBson().binaryEqual(input.toBson())); } @@ -859,6 +892,8 @@ TEST_F(SerializationTest, MetaSerializationNoVals) { docBuilder.metadata().setSearchScore(30.0); docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd << "def"_sd)); + docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails" + << "foo")); assertRoundTrips(docBuilder.freeze()); } @@ -871,6 +906,8 @@ TEST_F(SerializationTest, MetaSerializationWithVals) { docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd << "def"_sd)); docBuilder.metadata().setIndexKey(BSON("key" << 42)); + docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails" + << "foo")); assertRoundTrips(docBuilder.freeze()); } @@ -891,6 +928,8 @@ TEST(MetaFields, ToAndFromBson) { docBuilder.metadata().setSearchScore(30.0); docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd << "def"_sd)); + docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails" + << "foo")); Document doc = docBuilder.freeze(); BSONObj obj = doc.toBsonWithMetaData(); ASSERT_EQ(10.0, obj[Document::metaFieldTextScore].Double()); @@ -899,11 +938,17 @@ TEST(MetaFields, ToAndFromBson) { ASSERT_BSONOBJ_EQ(obj[Document::metaFieldSearchHighlights].embeddedObject(), BSON_ARRAY("abc"_sd << "def"_sd)); + ASSERT_BSONOBJ_EQ(obj[Document::metaFieldSearchScoreDetails].Obj(), + BSON("scoreDetails" + << "foo")); Document fromBson = Document::fromBsonWithMetaData(obj); ASSERT_TRUE(fromBson.metadata().hasTextScore()); ASSERT_TRUE(fromBson.metadata().hasRandVal()); ASSERT_EQ(10.0, fromBson.metadata().getTextScore()); ASSERT_EQ(20, fromBson.metadata().getRandVal()); + ASSERT_BSONOBJ_EQ(BSON("scoreDetails" + << "foo"), + fromBson.metadata().getSearchScoreDetails()); } TEST(MetaFields, MetaFieldsIncludedInDocumentApproximateSize) { @@ -922,7 +967,7 @@ TEST(MetaFields, MetaFieldsIncludedInDocumentApproximateSize) { ASSERT_GT(bigMetadataDocSize, smallMetadataDocSize); // Do a sanity check on the amount of space taken by metadata in document 2. - ASSERT_LT(doc2.getMetadataApproximateSize(), 250U); + ASSERT_LT(doc2.getMetadataApproximateSize(), 300U); Document emptyDoc; ASSERT_LT(emptyDoc.getMetadataApproximateSize(), 100U); diff --git a/src/mongo/db/exec/exclusion_projection_executor_test.cpp b/src/mongo/db/exec/exclusion_projection_executor_test.cpp index 7ee6b00ccf2..51893b71838 100644 --- a/src/mongo/db/exec/exclusion_projection_executor_test.cpp +++ b/src/mongo/db/exec/exclusion_projection_executor_test.cpp @@ -324,7 +324,8 @@ TEST(ExclusionProjectionExecutionTest, ShouldEvaluateMetaExpressions) { "h: {$meta: 'geoNearPoint'}, " "i: {$meta: 'recordId'}, " "j: {$meta: 'indexKey'}, " - "k: {$meta: 'sortKey'}}")); + "k: {$meta: 'sortKey'}, " + "l: {$meta: 'searchScoreDetails'}}")); MutableDocument inputDocBuilder(Document{{"a", 1}, {"b", 2}}); inputDocBuilder.metadata().setTextScore(0.0); @@ -336,13 +337,16 @@ TEST(ExclusionProjectionExecutionTest, ShouldEvaluateMetaExpressions) { inputDocBuilder.metadata().setRecordId(RecordId{6}); inputDocBuilder.metadata().setIndexKey(BSON("foo" << 7)); inputDocBuilder.metadata().setSortKey(Value{Document{{"bar", 8}}}, true); + inputDocBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails" + << "foo")); Document inputDoc = inputDocBuilder.freeze(); auto result = exclusion->applyTransformation(inputDoc); ASSERT_DOCUMENT_EQ(result, Document{fromjson("{b: 2, c: 0.0, d: 1.0, e: 2.0, f: 'foo', g: 3.0, " - "h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}]}")}); + "h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}]," + "l: {scoreDetails: 'foo'}}")}); } TEST(ExclusionProjectionExecutionTest, ShouldAddMetaExpressionsToDependencies) { @@ -355,17 +359,19 @@ TEST(ExclusionProjectionExecutionTest, ShouldAddMetaExpressionsToDependencies) { "h: {$meta: 'geoNearPoint'}, " "i: {$meta: 'recordId'}, " "j: {$meta: 'indexKey'}, " - "k: {$meta: 'sortKey'}}")); + "k: {$meta: 'sortKey'}, " + "l: {$meta: 'searchScoreDetails'}}")); DepsTracker deps; exclusion->addDependencies(&deps); ASSERT_EQ(deps.fields.size(), 0UL); - // We do not add the dependencies for SEARCH_SCORE or SEARCH_HIGHLIGHTS because those values - // are not stored in the collection (or in mongod at all). + // We do not add the dependencies for searchScore, searchHighlights, or searchScoreDetails + // because those values are not stored in the collection (or in mongod at all). ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScore]); ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchHighlights]); + ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScoreDetails]); ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kTextScore]); ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kRandVal]); diff --git a/src/mongo/db/exec/inclusion_projection_executor_test.cpp b/src/mongo/db/exec/inclusion_projection_executor_test.cpp index de2464f7f11..1b61ab11f9b 100644 --- a/src/mongo/db/exec/inclusion_projection_executor_test.cpp +++ b/src/mongo/db/exec/inclusion_projection_executor_test.cpp @@ -776,17 +776,19 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, "h: {$meta: 'geoNearPoint'}, " "i: {$meta: 'recordId'}, " "j: {$meta: 'indexKey'}, " - "k: {$meta: 'sortKey'}}")); + "k: {$meta: 'sortKey'}, " + "l: {$meta: 'searchScoreDetails'}}")); DepsTracker deps; inclusion->addDependencies(&deps); ASSERT_EQ(deps.fields.size(), 2UL); - // We do not add the dependencies for SEARCH_SCORE or SEARCH_HIGHLIGHTS because those - // values are not stored in the collection (or in mongod at all). + // We do not add the dependencies for searchScore, searchHighlights, or searchScoreDetails + // because those values are not stored in the collection (or in mongod at all). ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScore]); ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchHighlights]); + ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScoreDetails]); ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kTextScore]); ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kRandVal]); @@ -807,7 +809,8 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldEvaluateMeta "h: {$meta: 'geoNearPoint'}, " "i: {$meta: 'recordId'}, " "j: {$meta: 'indexKey'}, " - "k: {$meta: 'sortKey'}}")); + "k: {$meta: 'sortKey'}, " + "l: {$meta: 'searchScoreDetails'}}")); MutableDocument inputDocBuilder(Document{{"a", 1}}); inputDocBuilder.metadata().setTextScore(0.0); @@ -819,13 +822,16 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldEvaluateMeta inputDocBuilder.metadata().setRecordId(RecordId{6}); inputDocBuilder.metadata().setIndexKey(BSON("foo" << 7)); inputDocBuilder.metadata().setSortKey(Value{Document{{"bar", 8}}}, true); + inputDocBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails" + << "foo")); Document inputDoc = inputDocBuilder.freeze(); auto result = inclusion->applyTransformation(inputDoc); ASSERT_DOCUMENT_EQ(result, Document{fromjson("{a: 1, c: 0.0, d: 1.0, e: 2.0, f: 'foo', g: 3.0, " - "h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}]}")}); + "h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}], " + "l: {scoreDetails: 'foo'}}")}); } // diff --git a/src/mongo/db/index/sort_key_generator_test.cpp b/src/mongo/db/index/sort_key_generator_test.cpp index 4fc3694974a..2e6641f000d 100644 --- a/src/mongo/db/index/sort_key_generator_test.cpp +++ b/src/mongo/db/index/sort_key_generator_test.cpp @@ -226,6 +226,15 @@ TEST(SortKeyGeneratorTest, SortPatternComponentWithSearchHighlightsMetaKeywordUa "$meta sort by 'searchHighlights' metadata is not supported"); } +TEST(SortKeyGeneratorTest, SortPatternComponentWithSearchScoreDetailsMetaKeywordUasserts) { + ASSERT_THROWS_CODE_AND_WHAT(makeSortKeyGen(BSON("a" << BSON("$meta" + << "searchScoreDetails")), + nullptr), + AssertionException, + 31138, + "Illegal $meta sort: $meta: \"searchScoreDetails\""); +} + TEST(SortKeyGeneratorTest, CanGenerateKeysForTextScoreMetaSort) { auto sortKeyGen = makeSortKeyGen(BSON("a" << BSON("$meta" << "textScore")), diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 0cc4961c590..49f55dda847 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -2584,6 +2584,7 @@ const std::string geoNearPointName = "geoNearPoint"; const std::string recordIdName = "recordId"; const std::string indexKeyName = "indexKey"; const std::string sortKeyName = "sortKey"; +const std::string searchScoreDetailsName = "searchScoreDetails"; using MetaType = DocumentMetadataFields::MetaType; const StringMap<DocumentMetadataFields::MetaType> kMetaNameToMetaType = { @@ -2594,6 +2595,7 @@ const StringMap<DocumentMetadataFields::MetaType> kMetaNameToMetaType = { {recordIdName, MetaType::kRecordId}, {searchHighlightsName, MetaType::kSearchHighlights}, {searchScoreName, MetaType::kSearchScore}, + {searchScoreDetailsName, MetaType::kSearchScoreDetails}, {sortKeyName, MetaType::kSortKey}, {textScoreName, MetaType::kTextScore}, }; @@ -2606,6 +2608,7 @@ const stdx::unordered_map<DocumentMetadataFields::MetaType, StringData> kMetaTyp {MetaType::kRecordId, recordIdName}, {MetaType::kSearchHighlights, searchHighlightsName}, {MetaType::kSearchScore, searchScoreName}, + {MetaType::kSearchScoreDetails, searchScoreDetailsName}, {MetaType::kSortKey, sortKeyName}, {MetaType::kTextScore, textScoreName}, }; @@ -2663,6 +2666,9 @@ Value ExpressionMeta::evaluate(const Document& root, Variables* variables) const ? Value(DocumentMetadataFields::serializeSortKey(metadata.isSingleElementKey(), metadata.getSortKey())) : Value(); + case MetaType::kSearchScoreDetails: + return metadata.hasSearchScoreDetails() ? Value(metadata.getSearchScoreDetails()) + : Value(); default: MONGO_UNREACHABLE; } @@ -2670,9 +2676,10 @@ Value ExpressionMeta::evaluate(const Document& root, Variables* variables) const } void ExpressionMeta::_doAddDependencies(DepsTracker* deps) const { - if (_metaType == MetaType::kSearchScore || _metaType == MetaType::kSearchHighlights) { - // We do not add the dependencies for SEARCH_SCORE or SEARCH_HIGHLIGHTS because those values - // are not stored in the collection (or in mongod at all). + if (_metaType == MetaType::kSearchScore || _metaType == MetaType::kSearchHighlights || + _metaType == MetaType::kSearchScoreDetails) { + // We do not add the dependencies for searchScore, searchHighlights, or searchScoreDetails + // because those values are not stored in the collection (or in mongod at all). return; } diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index 7c80233b130..19fbd6e68bf 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -2680,6 +2680,20 @@ TEST(ExpressionMetaTest, ExpressionMetaTextScore) { Value val = expressionMeta->evaluate(doc.freeze(), &expCtx.variables); ASSERT_EQ(val.getDouble(), 1.23); } + +TEST(ExpressionMetaTest, ExpressionMetaSearchScoreDetails) { + auto expCtx = ExpressionContextForTest{}; + BSONObj expr = fromjson("{$meta: \"searchScoreDetails\"}"); + auto expressionMeta = + ExpressionMeta::parse(&expCtx, expr.firstElement(), expCtx.variablesParseState); + + auto details = BSON("scoreDetails" + << "foo"); + MutableDocument doc; + doc.metadata().setSearchScoreDetails(details); + Value val = expressionMeta->evaluate(doc.freeze(), &expCtx.variables); + ASSERT_DOCUMENT_EQ(val.getDocument(), Document(details)); +} } // namespace expression_meta_test namespace ExpressionRegexTest { |