diff options
48 files changed, 1065 insertions, 857 deletions
diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript index 7ee2806711e..29ff41d620c 100644 --- a/src/mongo/db/exec/SConscript +++ b/src/mongo/db/exec/SConscript @@ -9,11 +9,11 @@ env.Library( target = "working_set", source = [ "working_set.cpp", - "working_set_computed_data.cpp" ], LIBDEPS = [ "$BUILD_DIR/mongo/base", "$BUILD_DIR/mongo/db/bson/dotted_path_support", + "$BUILD_DIR/mongo/db/pipeline/document_value", "$BUILD_DIR/mongo/db/service_context", ], ) diff --git a/src/mongo/db/exec/and_common.h b/src/mongo/db/exec/and_common.h index beb84e5751f..fe7a0573694 100644 --- a/src/mongo/db/exec/and_common.h +++ b/src/mongo/db/exec/and_common.h @@ -52,13 +52,7 @@ public: verify(src.hasRecordId()); verify(dest->recordId == src.recordId); - // Merge computed data. - typedef WorkingSetComputedDataType WSCD; - for (WSCD i = WSCD(0); i < WSM_COMPUTED_NUM_TYPES; i = WSCD(i + 1)) { - if (!dest->hasComputed(i) && src.hasComputed(i)) { - dest->addComputed(src.getComputed(i)->clone()); - } - } + dest->metadata().mergeWith(src.metadata()); if (dest->hasObj()) { // The merged WSM that we're creating already has the full document, so there's diff --git a/src/mongo/db/exec/change_stream_proxy.cpp b/src/mongo/db/exec/change_stream_proxy.cpp index 62e6de4aba6..d4f5ae39c6f 100644 --- a/src/mongo/db/exec/change_stream_proxy.cpp +++ b/src/mongo/db/exec/change_stream_proxy.cpp @@ -56,7 +56,7 @@ boost::optional<BSONObj> ChangeStreamProxyStage::getNextBson() { // the latest event observed in the oplog, the latter via its sort key metadata field. auto nextBSON = _validateAndConvertToBSON(*next); _latestOplogTimestamp = PipelineD::getLatestOplogTimestamp(_pipeline.get()); - _postBatchResumeToken = next->getSortKeyMetaField(); + _postBatchResumeToken = next->metadata().getSortKey(); _setSpeculativeReadTimestamp(); return nextBSON; } @@ -83,7 +83,7 @@ BSONObj ChangeStreamProxyStage::_validateAndConvertToBSON(const Document& event) } // Confirm that the document _id field matches the original resume token in the sort key field. auto eventBSON = event.toBson(); - auto resumeToken = event.getSortKeyMetaField(); + auto resumeToken = event.metadata().getSortKey(); auto idField = eventBSON.getObjectField("_id"); invariant(!resumeToken.isEmpty()); uassert(ErrorCodes::ChangeStreamFatalError, diff --git a/src/mongo/db/exec/distinct_scan.cpp b/src/mongo/db/exec/distinct_scan.cpp index af7d3c49cfa..22af764672f 100644 --- a/src/mongo/db/exec/distinct_scan.cpp +++ b/src/mongo/db/exec/distinct_scan.cpp @@ -35,7 +35,6 @@ #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/exec/filter.h" #include "mongo/db/exec/scoped_timer.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/index_access_method.h" #include "mongo/db/index/index_descriptor.h" diff --git a/src/mongo/db/exec/ensure_sorted.cpp b/src/mongo/db/exec/ensure_sorted.cpp index d10e7e95308..611835aaba6 100644 --- a/src/mongo/db/exec/ensure_sorted.cpp +++ b/src/mongo/db/exec/ensure_sorted.cpp @@ -34,7 +34,6 @@ #include <memory> #include "mongo/db/exec/scoped_timer.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/query/find_common.h" namespace mongo { @@ -60,12 +59,10 @@ PlanStage::StageState EnsureSortedStage::doWork(WorkingSetID* out) { StageState stageState = child()->work(out); if (PlanStage::ADVANCED == stageState) { - // We extract the sort key from the WSM's computed data. This must have been generated - // by a SortKeyGeneratorStage descendent in the execution tree. + // We extract the sort key from the WSM's metadata. This must have been generated by a + // SortKeyGeneratorStage descendent in the execution tree. WorkingSetMember* member = _ws->get(*out); - auto sortKeyComputedData = - static_cast<const SortKeyComputedData*>(member->getComputed(WSM_SORT_KEY)); - BSONObj curSortKey = sortKeyComputedData->getSortKey(); + auto curSortKey = member->metadata().getSortKey(); invariant(!curSortKey.isEmpty()); if (!_prevSortKey.isEmpty() && !isInOrder(_prevSortKey, curSortKey)) { diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp index f3f08bd3be8..df28f3edcdb 100644 --- a/src/mongo/db/exec/geo_near.cpp +++ b/src/mongo/db/exec/geo_near.cpp @@ -41,12 +41,12 @@ #include "mongo/db/bson/dotted_path_support.h" #include "mongo/db/exec/fetch.h" #include "mongo/db/exec/index_scan.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/geo/geoconstants.h" #include "mongo/db/geo/geoparser.h" #include "mongo/db/geo/hash.h" #include "mongo/db/index/expression_params.h" #include "mongo/db/matcher/expression.h" +#include "mongo/db/pipeline/value.h" #include "mongo/db/query/expression_index.h" #include "mongo/db/query/expression_index_knobs_gen.h" #include "mongo/util/log.h" @@ -155,7 +155,7 @@ static StatusWith<double> computeGeoNearDistance(const GeoNearParams& nearParams // Compute the minimum distance of all the geometries in the document double minDistance = -1; - BSONObj minDistanceObj; + Value minDistanceMetadata; for (auto it = geometries.begin(); it != geometries.end(); ++it) { StoredGeometry& stored = **it; @@ -175,7 +175,7 @@ static StatusWith<double> computeGeoNearDistance(const GeoNearParams& nearParams if (minDistance < 0 || nextDistance < minDistance) { minDistance = nextDistance; - minDistanceObj = stored.element.Obj(); + minDistanceMetadata = Value{stored.element}; } } @@ -189,14 +189,14 @@ static StatusWith<double> computeGeoNearDistance(const GeoNearParams& nearParams // Hack for nearSphere // TODO: Remove nearSphere? invariant(SPHERE == queryCRS); - member->addComputed(new GeoDistanceComputedData(minDistance / kRadiusOfEarthInMeters)); + member->metadata().setGeoNearDistance(minDistance / kRadiusOfEarthInMeters); } else { - member->addComputed(new GeoDistanceComputedData(minDistance)); + member->metadata().setGeoNearDistance(minDistance); } } if (nearParams.addPointMeta) { - member->addComputed(new GeoNearPointComputedData(minDistanceObj)); + member->metadata().setGeoNearPoint(minDistanceMetadata); } return StatusWith<double>(minDistance); diff --git a/src/mongo/db/exec/idhack.cpp b/src/mongo/db/exec/idhack.cpp index b287dc12531..13a5837b53d 100644 --- a/src/mongo/db/exec/idhack.cpp +++ b/src/mongo/db/exec/idhack.cpp @@ -39,7 +39,6 @@ #include "mongo/db/exec/projection.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/btree_access_method.h" namespace mongo { @@ -133,8 +132,7 @@ PlanStage::StageState IDHackStage::advance(WorkingSetID id, if (_addKeyMetadata) { BSONObj ownedKeyObj = member->obj.value()["_id"].wrap().getOwned(); - member->addComputed( - new IndexKeyComputedData(IndexKeyComputedData::rehydrateKey(_key, ownedKeyObj))); + member->metadata().setIndexKey(IndexKeyEntry::rehydrateKey(_key, ownedKeyObj)); } _done = true; diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp index f74743629bb..7e5c9c19152 100644 --- a/src/mongo/db/exec/index_scan.cpp +++ b/src/mongo/db/exec/index_scan.cpp @@ -39,7 +39,6 @@ #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/exec/filter.h" #include "mongo/db/exec/scoped_timer.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/index_access_method.h" #include "mongo/db/index_names.h" #include "mongo/db/query/index_bounds_builder.h" @@ -217,8 +216,7 @@ PlanStage::StageState IndexScan::doWork(WorkingSetID* out) { _workingSet->transitionToRecordIdAndIdx(id); if (_addKeyMetadata) { - member->addComputed( - new IndexKeyComputedData(IndexKeyComputedData::rehydrateKey(_keyPattern, kv->key))); + member->metadata().setIndexKey(IndexKeyEntry::rehydrateKey(_keyPattern, kv->key)); } *out = id; diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp index eb2933ffa63..f48a09ffb02 100644 --- a/src/mongo/db/exec/projection.cpp +++ b/src/mongo/db/exec/projection.cpp @@ -37,7 +37,6 @@ #include "mongo/db/exec/plan_stage.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/record_id.h" @@ -51,33 +50,30 @@ static const char* kIdField = "_id"; namespace { BSONObj indexKey(const WorkingSetMember& member) { - return static_cast<const IndexKeyComputedData*>(member.getComputed(WSM_INDEX_KEY))->getKey(); + return member.metadata().getIndexKey(); } BSONObj sortKey(const WorkingSetMember& member) { - return static_cast<const SortKeyComputedData*>(member.getComputed(WSM_SORT_KEY))->getSortKey(); + return member.metadata().getSortKey(); } double geoDistance(const WorkingSetMember& member) { - return static_cast<const GeoDistanceComputedData*>( - member.getComputed(WSM_COMPUTED_GEO_DISTANCE)) - ->getDist(); + return member.metadata().getGeoNearDistance(); } -BSONObj geoPoint(const WorkingSetMember& member) { - return static_cast<const GeoNearPointComputedData*>(member.getComputed(WSM_GEO_NEAR_POINT)) - ->getPoint(); +Value geoPoint(const WorkingSetMember& member) { + return member.metadata().getGeoNearPoint(); } double textScore(const WorkingSetMember& member) { - if (member.hasComputed(WSM_COMPUTED_TEXT_SCORE)) - return static_cast<const TextScoreComputedData*>( - member.getComputed(WSM_COMPUTED_TEXT_SCORE)) - ->getScore(); - // It is permitted to request a text score when none has been computed. Zero is returned as an - // empty value in this case. - else + auto&& metadata = member.metadata(); + if (metadata.hasTextScore()) { + return metadata.getTextScore(); + } else { + // It is permitted to request a text score when none has been computed. Zero is returned as + // an empty value in this case. return 0.0; + } } void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) { @@ -89,10 +85,10 @@ void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) { StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, const WorkingSetMember& member) { - if (exec.needsGeoNearDistance() && !member.hasComputed(WSM_COMPUTED_GEO_DISTANCE)) + if (exec.needsGeoNearDistance() && !member.metadata().hasGeoNearDistance()) return Status(ErrorCodes::InternalError, "near loc dist requested but no data available"); - if (exec.needsGeoNearPoint() && !member.hasComputed(WSM_GEO_NEAR_POINT)) + if (exec.needsGeoNearPoint() && !member.metadata().hasGeoNearPoint()) return Status(ErrorCodes::InternalError, "near loc proj requested but no data available"); return member.hasObj() @@ -100,7 +96,7 @@ StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, exec.needsGeoNearDistance() ? boost::optional<const double>(geoDistance(member)) : boost::none, - exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(), + exec.needsGeoNearPoint() ? geoPoint(member) : Value{}, exec.needsSortKey() ? sortKey(member) : BSONObj(), exec.needsTextScore() ? boost::optional<const double>(textScore(member)) : boost::none, @@ -109,7 +105,7 @@ StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, member.keyData, exec.needsGeoNearDistance() ? boost::optional<const double>(geoDistance(member)) : boost::none, - exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(), + exec.needsGeoNearPoint() ? geoPoint(member) : Value{}, exec.needsSortKey() ? sortKey(member) : BSONObj(), exec.needsTextScore() ? boost::optional<const double>(textScore(member)) : boost::none, @@ -204,13 +200,13 @@ ProjectionStageDefault::ProjectionStageDefault(OperationContext* opCtx, Status ProjectionStageDefault::transform(WorkingSetMember* member) const { // The default no-fast-path case. - if (_exec.needsSortKey() && !member->hasComputed(WSM_SORT_KEY)) + if (_exec.needsSortKey() && !member->metadata().hasSortKey()) return Status(ErrorCodes::InternalError, "sortKey meta-projection requested but no data available"); if (_exec.returnKey()) { auto keys = _exec.computeReturnKeyProjection( - member->hasComputed(WSM_INDEX_KEY) ? indexKey(*member) : BSONObj(), + member->metadata().hasIndexKey() ? indexKey(*member) : BSONObj(), _exec.needsSortKey() ? sortKey(*member) : BSONObj()); if (!keys.isOK()) return keys.getStatus(); diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp index aae286bb318..736dd900545 100644 --- a/src/mongo/db/exec/projection_exec.cpp +++ b/src/mongo/db/exec/projection_exec.cpp @@ -218,7 +218,7 @@ StatusWith<BSONObj> ProjectionExec::computeReturnKeyProjection(const BSONObj& in StatusWith<BSONObj> ProjectionExec::project(const BSONObj& in, const boost::optional<const double> geoDistance, - const BSONObj& geoNearPoint, + Value geoNearPoint, const BSONObj& sortKey, const boost::optional<const double> textScore, const int64_t recordId) const { @@ -241,7 +241,7 @@ StatusWith<BSONObj> ProjectionExec::project(const BSONObj& in, StatusWith<BSONObj> ProjectionExec::projectCovered(const std::vector<IndexKeyDatum>& keyData, const boost::optional<const double> geoDistance, - const BSONObj& geoNearPoint, + Value geoNearPoint, const BSONObj& sortKey, const boost::optional<const double> textScore, const int64_t recordId) const { @@ -294,7 +294,7 @@ StatusWith<BSONObj> ProjectionExec::projectCovered(const std::vector<IndexKeyDat BSONObj ProjectionExec::addMeta(BSONObjBuilder bob, const boost::optional<const double> geoDistance, - const BSONObj& geoNearPoint, + Value geoNearPoint, const BSONObj& sortKey, const boost::optional<const double> textScore, const int64_t recordId) const { @@ -305,13 +305,8 @@ BSONObj ProjectionExec::addMeta(BSONObjBuilder bob, bob.append(it->first, geoDistance.get()); break; case META_GEONEAR_POINT: { - invariant(!geoNearPoint.isEmpty()); - auto& ptObj = geoNearPoint; - if (ptObj.couldBeArray()) { - bob.appendArray(it->first, ptObj); - } else { - bob.append(it->first, ptObj); - } + invariant(!geoNearPoint.missing()); + geoNearPoint.addToBsonObj(&bob, it->first); break; } case META_TEXT_SCORE: diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h index ab4dc5f48dc..a6ce7b1317c 100644 --- a/src/mongo/db/exec/projection_exec.h +++ b/src/mongo/db/exec/projection_exec.h @@ -35,6 +35,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_parser.h" +#include "mongo/db/pipeline/value.h" #include "mongo/util/string_map.h" namespace mongo { @@ -134,7 +135,7 @@ public: */ StatusWith<BSONObj> project(const BSONObj& in, const boost::optional<const double> geoDistance = boost::none, - const BSONObj& geoNearPoint = BSONObj(), + Value geoNearPoint = Value{}, const BSONObj& sortKey = BSONObj(), const boost::optional<const double> textScore = boost::none, const int64_t recordId = 0) const; @@ -147,7 +148,7 @@ public: StatusWith<BSONObj> projectCovered( const std::vector<IndexKeyDatum>& keyData, const boost::optional<const double> geoDistance = boost::none, - const BSONObj& geoNearPoint = BSONObj(), + Value geoNearPoint = Value{}, const BSONObj& sortKey = BSONObj(), const boost::optional<const double> textScore = boost::none, const int64_t recordId = 0) const; @@ -167,7 +168,7 @@ private: */ BSONObj addMeta(BSONObjBuilder bob, const boost::optional<const double> geoDistance, - const BSONObj& geoNearPoint, + Value geoNearPoint, const BSONObj& sortKey, const boost::optional<const double> textScore, const int64_t recordId) const; diff --git a/src/mongo/db/exec/projection_exec_test.cpp b/src/mongo/db/exec/projection_exec_test.cpp index 9c04e20c2d0..33e14d01406 100644 --- a/src/mongo/db/exec/projection_exec_test.cpp +++ b/src/mongo/db/exec/projection_exec_test.cpp @@ -37,7 +37,6 @@ #include "mongo/db/exec/projection_exec.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" @@ -81,10 +80,10 @@ boost::optional<std::string> project( auto objStr = stdx::get_if<const char*>(&objStrOrDatum); auto projected = objStr - ? exec.project(fromjson(*objStr), boost::none, BSONObj(), sortKey, textScore) + ? exec.project(fromjson(*objStr), boost::none, Value{}, sortKey, textScore) : exec.projectCovered({stdx::get<const IndexKeyDatum>(objStrOrDatum)}, boost::none, - BSONObj(), + Value{}, sortKey, textScore); diff --git a/src/mongo/db/exec/sort.cpp b/src/mongo/db/exec/sort.cpp index 9bf39c08773..13f1615ee11 100644 --- a/src/mongo/db/exec/sort.cpp +++ b/src/mongo/db/exec/sort.cpp @@ -37,7 +37,6 @@ #include "mongo/db/catalog/collection.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/btree_key_generator.h" #include "mongo/db/index_names.h" #include "mongo/db/query/find_common.h" @@ -126,11 +125,9 @@ PlanStage::StageState SortStage::doWork(WorkingSetID* out) { SortableDataItem item; item.wsid = id; - // We extract the sort key from the WSM's computed data. This must have been generated - // by a SortKeyGeneratorStage descendent in the execution tree. - auto sortKeyComputedData = - static_cast<const SortKeyComputedData*>(member->getComputed(WSM_SORT_KEY)); - item.sortKey = sortKeyComputedData->getSortKey(); + // We extract the sort key from the WSM's metadata. This must have been generated by a + // SortKeyGeneratorStage descendent in the execution tree. + item.sortKey = member->metadata().getSortKey(); if (member->hasRecordId()) { // The RecordId breaks ties when sorting two WSMs with the same sort key. diff --git a/src/mongo/db/exec/sort_key_generator.cpp b/src/mongo/db/exec/sort_key_generator.cpp index 1aa3abf8660..7b846f18bad 100644 --- a/src/mongo/db/exec/sort_key_generator.cpp +++ b/src/mongo/db/exec/sort_key_generator.cpp @@ -41,7 +41,6 @@ #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/query/collation/collator_interface.h" #include "mongo/util/log.h" @@ -74,8 +73,8 @@ PlanStage::StageState SortKeyGeneratorStage::doWork(WorkingSetID* out) { return PlanStage::FAILURE; } - // Add the sort key to the WSM as computed data. - member->addComputed(new SortKeyComputedData(sortKey.getValue())); + // Add the sort key to the WSM as metadata. + member->metadata().setSortKey(sortKey.getValue()); return PlanStage::ADVANCED; } diff --git a/src/mongo/db/exec/sort_key_generator.h b/src/mongo/db/exec/sort_key_generator.h index b5208ee78fb..79cb5a6fe49 100644 --- a/src/mongo/db/exec/sort_key_generator.h +++ b/src/mongo/db/exec/sort_key_generator.h @@ -45,7 +45,7 @@ class WorkingSetMember; /** * Passes results from the child through after adding the sort key for each result as - * WorkingSetMember computed data. + * WorkingSetMember metadata. */ class SortKeyGeneratorStage final : public PlanStage { public: diff --git a/src/mongo/db/exec/text_or.cpp b/src/mongo/db/exec/text_or.cpp index 2e3eb79d5a0..c945ccd2fcd 100644 --- a/src/mongo/db/exec/text_or.cpp +++ b/src/mongo/db/exec/text_or.cpp @@ -39,7 +39,6 @@ #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set.h" #include "mongo/db/exec/working_set_common.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/jsobj.h" #include "mongo/db/record_id.h" @@ -236,8 +235,8 @@ PlanStage::StageState TextOrStage::returnResults(WorkingSetID* out) { WorkingSetMember* wsm = _ws->get(textRecordData.wsid); - // Populate the working set member with the text score and return it. - wsm->addComputed(new TextScoreComputedData(textRecordData.score)); + // Populate the working set member with the text score metadata and return it. + wsm->metadata().setTextScore(textRecordData.score); *out = textRecordData.wsid; return PlanStage::ADVANCED; } diff --git a/src/mongo/db/exec/working_set.cpp b/src/mongo/db/exec/working_set.cpp index 79a0fbbcaa6..400be5acb83 100644 --- a/src/mongo/db/exec/working_set.cpp +++ b/src/mongo/db/exec/working_set.cpp @@ -125,10 +125,7 @@ WorkingSetMember::WorkingSetMember() {} WorkingSetMember::~WorkingSetMember() {} void WorkingSetMember::clear() { - for (size_t i = 0; i < WSM_COMPUTED_NUM_TYPES; i++) { - _computed[i].reset(); - } - + _metadata = DocumentMetadataFields{}; keyData.clear(); obj.reset(); _state = WorkingSetMember::INVALID; @@ -162,21 +159,6 @@ void WorkingSetMember::makeObjOwnedIfNeeded() { } } -bool WorkingSetMember::hasComputed(const WorkingSetComputedDataType type) const { - return _computed[type].get(); -} - -const WorkingSetComputedData* WorkingSetMember::getComputed( - const WorkingSetComputedDataType type) const { - verify(_computed[type]); - return _computed[type].get(); -} - -void WorkingSetMember::addComputed(WorkingSetComputedData* data) { - verify(!hasComputed(data->type())); - _computed[data->type()].reset(data); -} - bool WorkingSetMember::getFieldDotted(const string& field, BSONElement* out) const { // If our state is such that we have an object, use it. if (hasObj()) { diff --git a/src/mongo/db/exec/working_set.h b/src/mongo/db/exec/working_set.h index fc6a228036d..43814158fb5 100644 --- a/src/mongo/db/exec/working_set.h +++ b/src/mongo/db/exec/working_set.h @@ -33,6 +33,7 @@ #include <vector> #include "mongo/db/jsobj.h" +#include "mongo/db/pipeline/document_metadata_fields.h" #include "mongo/db/record_id.h" #include "mongo/db/storage/snapshot.h" #include "mongo/stdx/unordered_set.h" @@ -181,51 +182,6 @@ struct IndexKeyDatum { }; /** - * What types of computed data can we have? - */ -enum WorkingSetComputedDataType { - // What's the score of the document retrieved from a $text query? - WSM_COMPUTED_TEXT_SCORE = 0, - - // What's the distance from a geoNear query point to the document? - WSM_COMPUTED_GEO_DISTANCE = 1, - - // The index key used to retrieve the document, for returnKey query option. - WSM_INDEX_KEY = 2, - - // What point (of several possible points) was used to compute the distance to the document - // via geoNear? - WSM_GEO_NEAR_POINT = 3, - - // Comparison key for sorting. - WSM_SORT_KEY = 4, - - // Must be last. - WSM_COMPUTED_NUM_TYPES, -}; - -/** - * Data that is a computed function of a WSM. - */ -class WorkingSetComputedData { - WorkingSetComputedData(const WorkingSetComputedData&) = delete; - WorkingSetComputedData& operator=(const WorkingSetComputedData&) = delete; - -public: - WorkingSetComputedData(const WorkingSetComputedDataType type) : _type(type) {} - virtual ~WorkingSetComputedData() {} - - WorkingSetComputedDataType type() const { - return _type; - } - - virtual WorkingSetComputedData* clone() const = 0; - -private: - WorkingSetComputedDataType _type; -}; - -/** * The type of the data passed between query stages. In particular: * * Index scan stages return a WorkingSetMember in the RID_AND_IDX state. @@ -297,14 +253,6 @@ public: */ void makeObjOwnedIfNeeded(); - // - // Computed data - // - - bool hasComputed(const WorkingSetComputedDataType type) const; - const WorkingSetComputedData* getComputed(const WorkingSetComputedDataType type) const; - void addComputed(WorkingSetComputedData* data); - /** * getFieldDotted uses its state (obj or index data) to produce the field with the provided * name. @@ -321,12 +269,28 @@ public: */ size_t getMemUsage() const; + /** + * Returns a const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + const DocumentMetadataFields& metadata() const { + return _metadata; + } + + /** + * Returns a non-const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + DocumentMetadataFields& metadata() { + return _metadata; + } + private: friend class WorkingSet; MemberState _state = WorkingSetMember::INVALID; - std::unique_ptr<WorkingSetComputedData> _computed[WSM_COMPUTED_NUM_TYPES]; + DocumentMetadataFields _metadata; }; } // namespace mongo diff --git a/src/mongo/db/exec/working_set_computed_data.cpp b/src/mongo/db/exec/working_set_computed_data.cpp deleted file mode 100644 index 5b8170778f8..00000000000 --- a/src/mongo/db/exec/working_set_computed_data.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault - -#include "mongo/db/exec/working_set_computed_data.h" - -#include "mongo/util/log.h" - -namespace mongo { - -BSONObj IndexKeyComputedData::rehydrateKey(const BSONObj& keyPattern, - const BSONObj& dehydratedKey) { - BSONObjBuilder bob; - BSONObjIterator keyIter(keyPattern); - BSONObjIterator valueIter(dehydratedKey); - - while (keyIter.more() && valueIter.more()) { - bob.appendAs(valueIter.next(), keyIter.next().fieldNameStringData()); - } - - invariant(!keyIter.more()); - invariant(!valueIter.more()); - - return bob.obj(); -} -} // namespace mongo diff --git a/src/mongo/db/exec/working_set_computed_data.h b/src/mongo/db/exec/working_set_computed_data.h deleted file mode 100644 index 3d71d4a815e..00000000000 --- a/src/mongo/db/exec/working_set_computed_data.h +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/exec/working_set.h" - -namespace mongo { - -class TextScoreComputedData : public WorkingSetComputedData { -public: - TextScoreComputedData(double score) - : WorkingSetComputedData(WSM_COMPUTED_TEXT_SCORE), _score(score) {} - - double getScore() const { - return _score; - } - - TextScoreComputedData* clone() const final { - return new TextScoreComputedData(_score); - } - -private: - double _score; -}; - -class GeoDistanceComputedData : public WorkingSetComputedData { -public: - GeoDistanceComputedData(double dist) - : WorkingSetComputedData(WSM_COMPUTED_GEO_DISTANCE), _dist(dist) {} - - double getDist() const { - return _dist; - } - - GeoDistanceComputedData* clone() const final { - return new GeoDistanceComputedData(_dist); - } - -private: - double _dist; -}; - -class IndexKeyComputedData : public WorkingSetComputedData { -public: - // Given an index key 'dehydratedKey' with no field names, returns a new BSONObj after adding - // field names according to 'keyPattern'. - static BSONObj rehydrateKey(const BSONObj& keyPattern, const BSONObj& dehydratedKey); - - IndexKeyComputedData(BSONObj key) - : WorkingSetComputedData(WSM_INDEX_KEY), _key(key.getOwned()) {} - - BSONObj getKey() const { - return _key; - } - - IndexKeyComputedData* clone() const final { - return new IndexKeyComputedData(_key); - } - -private: - BSONObj _key; -}; - -class GeoNearPointComputedData : public WorkingSetComputedData { -public: - GeoNearPointComputedData(BSONObj point) - : WorkingSetComputedData(WSM_GEO_NEAR_POINT), _point(point.getOwned()) {} - - BSONObj getPoint() const { - return _point; - } - - GeoNearPointComputedData* clone() const final { - return new GeoNearPointComputedData(_point); - } - -private: - BSONObj _point; -}; - -class SortKeyComputedData : public WorkingSetComputedData { -public: - SortKeyComputedData(BSONObj sortKey) - : WorkingSetComputedData(WSM_SORT_KEY), _sortKey(sortKey.getOwned()) {} - - BSONObj getSortKey() const { - return _sortKey; - } - - SortKeyComputedData* clone() const final { - return new SortKeyComputedData(_sortKey); - } - -private: - BSONObj _sortKey; -}; - -} // namespace mongo diff --git a/src/mongo/db/index/sort_key_generator.cpp b/src/mongo/db/index/sort_key_generator.cpp index a9fd65038fb..c28caa0f47e 100644 --- a/src/mongo/db/index/sort_key_generator.cpp +++ b/src/mongo/db/index/sort_key_generator.cpp @@ -32,7 +32,6 @@ #include "mongo/db/index/sort_key_generator.h" #include "mongo/bson/bsonobj_comparator.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/query/collation/collation_index_key.h" namespace mongo { @@ -87,10 +86,8 @@ SortKeyGenerator::SortKeyGenerator(const BSONObj& sortSpec, const CollatorInterf StatusWith<BSONObj> SortKeyGenerator::getSortKey(const WorkingSetMember& wsm) const { if (wsm.hasObj()) { SortKeyGenerator::Metadata metadata; - if (_sortHasMeta && wsm.hasComputed(WSM_COMPUTED_TEXT_SCORE)) { - auto scoreData = - static_cast<const TextScoreComputedData*>(wsm.getComputed(WSM_COMPUTED_TEXT_SCORE)); - metadata.textScore = scoreData->getScore(); + if (_sortHasMeta && wsm.metadata().hasTextScore()) { + metadata.textScore = wsm.metadata().getTextScore(); } return getSortKeyFromDocument(wsm.obj.value(), &metadata); } diff --git a/src/mongo/db/index/sort_key_generator_test.cpp b/src/mongo/db/index/sort_key_generator_test.cpp index a533a6cfbff..7801c911bd9 100644 --- a/src/mongo/db/index/sort_key_generator_test.cpp +++ b/src/mongo/db/index/sort_key_generator_test.cpp @@ -32,7 +32,6 @@ #include <memory> #include "mongo/bson/json.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/index/sort_key_generator.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/unittest/death_test.h" @@ -279,7 +278,7 @@ TEST_F(SortKeyGeneratorWorkingSetTest, CanGenerateKeyFromWSMForTextScoreMetaSort BSONObj pattern = fromjson("{a: 1, b: {$meta: 'textScore'}, c: -1}}"); auto sortKeyGen = std::make_unique<SortKeyGenerator>(pattern, nullptr); setOwnedObj(BSON("x" << 1 << "a" << 2 << "y" << 3 << "c" << BSON_ARRAY(4 << 5 << 6))); - member().addComputed(new TextScoreComputedData(9.9)); + member().metadata().setTextScore(9.9); auto sortKey = sortKeyGen->getSortKey(member()); ASSERT_OK(sortKey); ASSERT_BSONOBJ_EQ(BSON("" << 2 << "" << 9.9 << "" << 6), sortKey.getValue()); @@ -314,7 +313,7 @@ DEATH_TEST_F(SortKeyGeneratorWorkingSetTest, BSONObj pattern = fromjson("{z: {$meta: 'textScore'}}"); auto sortKeyGen = std::make_unique<SortKeyGenerator>(pattern, nullptr); setRecordIdAndIdx(BSON("a" << 1 << "b" << 1), BSON("" << 2 << "" << 3)); - member().addComputed(new TextScoreComputedData(9.9)); + member().metadata().setTextScore(9.9); MONGO_COMPILER_VARIABLE_UNUSED auto ignored = sortKeyGen->getSortKey(member()); } diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index 2d71d9dfadc..aae909141c1 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -31,6 +31,7 @@ env.Library( source=[ 'document.cpp', 'document_comparator.cpp', + 'document_metadata_fields.cpp', 'document_path_support.cpp', 'value.cpp', 'value_comparator.cpp', @@ -421,6 +422,7 @@ env.CppUnitTest( 'aggregation_request_test.cpp', 'dependencies_test.cpp', 'document_comparator_test.cpp', + 'document_metadata_fields_test.cpp', 'document_path_support_test.cpp', 'document_source_add_fields_test.cpp', 'document_source_bucket_auto_test.cpp', diff --git a/src/mongo/db/pipeline/document.cpp b/src/mongo/db/pipeline/document.cpp index 4906a51dab4..9557d01a414 100644 --- a/src/mongo/db/pipeline/document.cpp +++ b/src/mongo/db/pipeline/document.cpp @@ -51,7 +51,8 @@ const StringDataSet Document::allMetadataFieldNames{Document::metaFieldTextScore Document::metaFieldGeoNearDistance, Document::metaFieldGeoNearPoint, Document::metaFieldSearchScore, - Document::metaFieldSearchHighlights}; + Document::metaFieldSearchHighlights, + Document::metaFieldIndexKey}; DocumentStorageIterator::DocumentStorageIterator(DocumentStorage* storage, BSONObjIterator bsonIt) : _bsonIt(std::move(bsonIt)), @@ -322,46 +323,13 @@ intrusive_ptr<DocumentStorage> DocumentStorage::clone() const { dassert(out->_numFields == _numFields); } - // Copy metadata - if (_metadataFields) { - out->_metadataFields = std::make_unique<MetadataFields>(*_metadataFields); - } + out->_metadataFields = _metadataFields; return out; } -MetadataFields::MetadataFields(const MetadataFields& other) { - _metaFields = other._metaFields; - _textScore = other._textScore; - _randVal = other._randVal; - _sortKey = other._sortKey.getOwned(); - _geoNearDistance = other._geoNearDistance; - _geoNearPoint = other._geoNearPoint.getOwned(); - _searchScore = other._searchScore; - _searchHighlights = other._searchHighlights; -} - -size_t MetadataFields::getApproximateSize() const { - size_t size = sizeof(MetadataFields); - - // Count the "deep" portion of the metadata values. - size += _sortKey.objsize(); - size += _geoNearPoint.getApproximateSize(); - // Size of Value is double counted - once in sizeof(MetadataFields) and once in - // getApproximateSize() - size -= sizeof(_geoNearPoint); - size += _searchHighlights.getApproximateSize(); - size -= sizeof(_searchHighlights); - - return size; -} - size_t DocumentStorage::getMetadataApproximateSize() const { - if (!_metadataFields) { - return 0; - } - - return _metadataFields->getApproximateSize(); + return _metadataFields.getApproximateSize(); } DocumentStorage::~DocumentStorage() { @@ -377,25 +345,23 @@ void DocumentStorage::loadLazyMetadata() const { return; } - _metadataFields = std::make_unique<MetadataFields>(); - BSONObjIterator it(_bson); while (it.more()) { BSONElement elem(it.next()); auto fieldName = elem.fieldNameStringData(); if (fieldName[0] == '$') { if (fieldName == Document::metaFieldTextScore) { - _metadataFields->setTextScore(elem.Double()); + _metadataFields.setTextScore(elem.Double()); } else if (fieldName == Document::metaFieldSearchScore) { - _metadataFields->setSearchScore(elem.Double()); + _metadataFields.setSearchScore(elem.Double()); } else if (fieldName == Document::metaFieldSearchHighlights) { - _metadataFields->setSearchHighlights(Value(elem)); + _metadataFields.setSearchHighlights(Value(elem)); } else if (fieldName == Document::metaFieldRandVal) { - _metadataFields->setRandMetaField(elem.Double()); + _metadataFields.setRandVal(elem.Double()); } else if (fieldName == Document::metaFieldSortKey) { - _metadataFields->setSortKeyMetaField(elem.Obj()); + _metadataFields.setSortKey(elem.Obj()); } else if (fieldName == Document::metaFieldGeoNearDistance) { - _metadataFields->setGeoNearDistance(elem.Double()); + _metadataFields.setGeoNearDistance(elem.Double()); } else if (fieldName == Document::metaFieldGeoNearPoint) { Value val; if (elem.type() == BSONType::Array) { @@ -405,7 +371,9 @@ void DocumentStorage::loadLazyMetadata() const { val = Value(elem.embeddedObject()); } - _metadataFields->setGeoNearPoint(val); + _metadataFields.setGeoNearPoint(val); + } else if (fieldName == Document::metaFieldIndexKey) { + _metadataFields.setIndexKey(elem.Obj()); } } } @@ -472,20 +440,22 @@ constexpr StringData Document::metaFieldSearchHighlights; BSONObj Document::toBsonWithMetaData() const { BSONObjBuilder bb; toBson(&bb); - if (hasTextScore()) - bb.append(metaFieldTextScore, getTextScore()); - if (hasRandMetaField()) - bb.append(metaFieldRandVal, getRandMetaField()); - if (hasSortKeyMetaField()) - bb.append(metaFieldSortKey, getSortKeyMetaField()); - if (hasGeoNearDistance()) - bb.append(metaFieldGeoNearDistance, getGeoNearDistance()); - if (hasGeoNearPoint()) - getGeoNearPoint().addToBsonObj(&bb, metaFieldGeoNearPoint); - if (hasSearchScore()) - bb.append(metaFieldSearchScore, getSearchScore()); - if (hasSearchHighlights()) - getSearchHighlights().addToBsonObj(&bb, metaFieldSearchHighlights); + if (metadata().hasTextScore()) + bb.append(metaFieldTextScore, metadata().getTextScore()); + if (metadata().hasRandVal()) + bb.append(metaFieldRandVal, metadata().getRandVal()); + if (metadata().hasSortKey()) + bb.append(metaFieldSortKey, metadata().getSortKey()); + if (metadata().hasGeoNearDistance()) + bb.append(metaFieldGeoNearDistance, metadata().getGeoNearDistance()); + if (metadata().hasGeoNearPoint()) + metadata().getGeoNearPoint().addToBsonObj(&bb, metaFieldGeoNearPoint); + if (metadata().hasSearchScore()) + bb.append(metaFieldSearchScore, metadata().getSearchScore()); + if (metadata().hasSearchHighlights()) + metadata().getSearchHighlights().addToBsonObj(&bb, metaFieldSearchHighlights); + if (metadata().hasIndexKey()) + bb.append(metaFieldIndexKey, metadata().getIndexKey()); return bb.obj(); } @@ -573,7 +543,7 @@ size_t Document::getApproximateSize() const { } // The metadata also occupies space in the document storage that's pre-allocated. - size += getMetadataApproximateSize(); + size += storage().getMetadataApproximateSize(); size += storage().bsonObjSize(); return size; @@ -661,27 +631,7 @@ void Document::serializeForSorter(BufBuilder& buf) const { it->val.serializeForSorter(buf); } - if (hasTextScore()) { - buf.appendNum(char(MetaType::TEXT_SCORE + 1)); - buf.appendNum(getTextScore()); - } - if (hasRandMetaField()) { - buf.appendNum(char(MetaType::RAND_VAL + 1)); - buf.appendNum(getRandMetaField()); - } - if (hasSortKeyMetaField()) { - buf.appendNum(char(MetaType::SORT_KEY + 1)); - getSortKeyMetaField().appendSelfToBufBuilder(buf); - } - if (hasSearchScore()) { - buf.appendNum(char(MetaType::SEARCH_SCORE + 1)); - buf.appendNum(getSearchScore()); - } - if (hasSearchHighlights()) { - buf.appendNum(char(MetaType::SEARCH_HIGHLIGHTS + 1)); - getSearchHighlights().serializeForSorter(buf); - } - buf.appendNum(char(0)); + metadata().serializeForSorter(buf); } Document Document::deserializeForSorter(BufReader& buf, const SorterDeserializeSettings&) { @@ -692,23 +642,7 @@ Document Document::deserializeForSorter(BufReader& buf, const SorterDeserializeS doc.addField(name, Value::deserializeForSorter(buf, Value::SorterDeserializeSettings())); } - while (char marker = buf.read<char>()) { - if (marker == char(MetaType::TEXT_SCORE) + 1) { - doc.setTextScore(buf.read<LittleEndian<double>>()); - } else if (marker == char(MetaType::RAND_VAL) + 1) { - doc.setRandMetaField(buf.read<LittleEndian<double>>()); - } else if (marker == char(MetaType::SORT_KEY) + 1) { - doc.setSortKeyMetaField( - BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings())); - } else if (marker == char(MetaType::SEARCH_SCORE) + 1) { - doc.setSearchScore(buf.read<LittleEndian<double>>()); - } else if (marker == char(MetaType::SEARCH_HIGHLIGHTS) + 1) { - doc.setSearchHighlights( - Value::deserializeForSorter(buf, Value::SorterDeserializeSettings())); - } else { - uasserted(28744, "Unrecognized marker, unable to deserialize buffer"); - } - } + DocumentMetadataFields::deserializeForSorter(buf, &doc.metadata()); return doc.freeze(); } diff --git a/src/mongo/db/pipeline/document.h b/src/mongo/db/pipeline/document.h index 433466bf973..d89389dbabe 100644 --- a/src/mongo/db/pipeline/document.h +++ b/src/mongo/db/pipeline/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 metaFieldIndexKey = "$indexKey"_sd; static const StringDataSet allMetadataFieldNames; @@ -169,8 +170,7 @@ public: size_t getApproximateSize() const; /** - * Return the approximate amount of space used by metadata. Note that documents may reserve - * space for metadata even no metadata is used. + * Return the approximate amount of space used by metadata. */ size_t getMetadataApproximateSize() const { return storage().getMetadataApproximateSize(); @@ -234,12 +234,6 @@ public: */ static Document fromBsonWithMetaData(const BSONObj& bson); - /** - * Given a BSON object that may have metadata fields added as part of toBsonWithMetadata(), - * returns the same object without any of the metadata fields. - */ - static BSONObj stripMetadataFields(const BSONObj& bsonWithMetadata); - // Support BSONObjBuilder and BSONArrayBuilder "stream" API friend BSONObjBuilder& operator<<(BSONObjBuilderValueStream& builder, const Document& d); @@ -262,53 +256,12 @@ public: return Document(storage().clone().get()); } - bool hasTextScore() const { - return storage().hasTextScore(); - } - double getTextScore() const { - return storage().getTextScore(); - } - - bool hasRandMetaField() const { - return storage().hasRandMetaField(); - } - double getRandMetaField() const { - return storage().getRandMetaField(); - } - - bool hasSortKeyMetaField() const { - return storage().hasSortKeyMetaField(); - } - BSONObj getSortKeyMetaField() const { - return storage().getSortKeyMetaField(); - } - - bool hasGeoNearDistance() const { - return storage().hasGeoNearDistance(); - } - double getGeoNearDistance() const { - return storage().getGeoNearDistance(); - } - - bool hasGeoNearPoint() const { - return storage().hasGeoNearPoint(); - } - Value getGeoNearPoint() const { - return storage().getGeoNearPoint(); - } - - bool hasSearchScore() const { - return storage().hasSearchScore(); - } - double getSearchScore() const { - return storage().getSearchScore(); - } - - bool hasSearchHighlights() const { - return storage().hasSearchHighlights(); - } - Value getSearchHighlights() const { - return storage().getSearchHighlights(); + /** + * Returns a const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + const DocumentMetadataFields& metadata() const { + return storage().metadata(); } /// members for Sorter @@ -553,32 +506,12 @@ public: storage().copyMetaDataFrom(source.storage()); } - void setTextScore(double score) { - storage().setTextScore(score); - } - - void setRandMetaField(double val) { - storage().setRandMetaField(val); - } - - void setSortKeyMetaField(BSONObj sortKey) { - storage().setSortKeyMetaField(sortKey); - } - - void setGeoNearDistance(double dist) { - storage().setGeoNearDistance(dist); - } - - void setGeoNearPoint(Value point) { - storage().setGeoNearPoint(std::move(point)); - } - - void setSearchScore(double score) { - storage().setSearchScore(score); - } - - void setSearchHighlights(Value highlights) { - storage().setSearchHighlights(highlights); + /** + * Returns a non-const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + DocumentMetadataFields& metadata() { + return storage().metadata(); } /** Convert to a read-only document and release reference. diff --git a/src/mongo/db/pipeline/document_internal.h b/src/mongo/db/pipeline/document_internal.h index 6d7ecd7f675..f5dc33e0ae4 100644 --- a/src/mongo/db/pipeline/document_internal.h +++ b/src/mongo/db/pipeline/document_internal.h @@ -35,6 +35,7 @@ #include <boost/intrusive_ptr.hpp> #include "mongo/base/static_assert.h" +#include "mongo/db/pipeline/document_metadata_fields.h" #include "mongo/db/pipeline/value.h" #include "mongo/util/intrusive_counter.h" @@ -261,118 +262,6 @@ private: const ValueElement* _end; }; -enum MetaType : char { - TEXT_SCORE, - RAND_VAL, - SORT_KEY, - GEONEAR_DIST, - GEONEAR_POINT, - SEARCH_SCORE, - SEARCH_HIGHLIGHTS, - - // New fields must be added before the NUM_FIELDS sentinel. - NUM_FIELDS -}; - -/** - * A simple container of all metadata fields - * - */ -struct MetadataFields { - std::bitset<MetaType::NUM_FIELDS> _metaFields; - double _textScore{0.0}; - double _randVal{0.0}; - BSONObj _sortKey; - double _geoNearDistance{0.0}; - Value _geoNearPoint; - double _searchScore{0.0}; - Value _searchHighlights; - - MetadataFields() {} - // When adding a field, make sure to update the copy constructor. - MetadataFields(const MetadataFields& other); - - size_t getApproximateSize() const; - - bool hasTextScore() const { - return _metaFields.test(MetaType::TEXT_SCORE); - } - double getTextScore() const { - return _textScore; - } - void setTextScore(double score) { - _metaFields.set(MetaType::TEXT_SCORE); - _textScore = score; - } - - bool hasRandMetaField() const { - return _metaFields.test(MetaType::RAND_VAL); - } - double getRandMetaField() const { - return _randVal; - } - void setRandMetaField(double val) { - _metaFields.set(MetaType::RAND_VAL); - _randVal = val; - } - - bool hasSortKeyMetaField() const { - return _metaFields.test(MetaType::SORT_KEY); - } - BSONObj getSortKeyMetaField() const { - return _sortKey; - } - void setSortKeyMetaField(BSONObj sortKey) { - _metaFields.set(MetaType::SORT_KEY); - _sortKey = sortKey.getOwned(); - } - - bool hasGeoNearDistance() const { - return _metaFields.test(MetaType::GEONEAR_DIST); - } - double getGeoNearDistance() const { - return _geoNearDistance; - } - void setGeoNearDistance(double dist) { - _metaFields.set(MetaType::GEONEAR_DIST); - _geoNearDistance = dist; - } - - bool hasGeoNearPoint() const { - return _metaFields.test(MetaType::GEONEAR_POINT); - } - Value getGeoNearPoint() const { - return _geoNearPoint; - } - void setGeoNearPoint(Value point) { - _metaFields.set(MetaType::GEONEAR_POINT); - _geoNearPoint = std::move(point); - } - - bool hasSearchScore() const { - return _metaFields.test(MetaType::SEARCH_SCORE); - } - double getSearchScore() const { - return _searchScore; - } - void setSearchScore(double score) { - _metaFields.set(MetaType::SEARCH_SCORE); - _searchScore = score; - } - - bool hasSearchHighlights() const { - return _metaFields.test(MetaType::SEARCH_HIGHLIGHTS); - } - Value getSearchHighlights() const { - return _searchHighlights; - } - void setSearchHighlights(Value highlights) { - _metaFields.set(MetaType::SEARCH_HIGHLIGHTS); - _searchHighlights = highlights; - } -}; - - /// Storage class used by both Document and MutableDocument class DocumentStorage : public RefCountable { public: @@ -479,6 +368,7 @@ public: auto bsonObjSize() const { return _bson.objsize(); } + /** * Compute the space allocated for the metadata fields. Will account for space allocated for * unused metadata fields as well. @@ -490,124 +380,32 @@ public: * Note: does not clear metadata from this. */ void copyMetaDataFrom(const DocumentStorage& source) { - // It the underlying BSON object is shared and the source does not have metadata then + // If the underlying BSON object is shared and the source does not have metadata then // nothing needs to be copied. If the metadata is in the BSON then they are the same in // this and source. if (_bson.objdata() == source._bson.objdata() && !source._metadataFields) { return; } - if (source.hasTextScore()) { - setTextScore(source.getTextScore()); - } - if (source.hasRandMetaField()) { - setRandMetaField(source.getRandMetaField()); - } - if (source.hasSortKeyMetaField()) { - setSortKeyMetaField(source.getSortKeyMetaField()); - } - if (source.hasGeoNearDistance()) { - setGeoNearDistance(source.getGeoNearDistance()); - } - if (source.hasGeoNearPoint()) { - setGeoNearPoint(source.getGeoNearPoint()); - } - if (source.hasSearchScore()) { - setSearchScore(source.getSearchScore()); - } - if (source.hasSearchHighlights()) { - setSearchHighlights(source.getSearchHighlights()); - } - } - - bool hasTextScore() const { - loadLazyMetadata(); - return _metadataFields->hasTextScore(); - } - double getTextScore() const { - loadLazyMetadata(); - return _metadataFields->getTextScore(); - } - void setTextScore(double score) { - loadLazyMetadata(); - _metadataFields->setTextScore(score); - } - - bool hasRandMetaField() const { - loadLazyMetadata(); - return _metadataFields->hasRandMetaField(); - } - double getRandMetaField() const { - loadLazyMetadata(); - return _metadataFields->getRandMetaField(); - } - void setRandMetaField(double val) { - loadLazyMetadata(); - _metadataFields->setRandMetaField(val); - } - - bool hasSortKeyMetaField() const { - loadLazyMetadata(); - return _metadataFields->hasSortKeyMetaField(); - } - BSONObj getSortKeyMetaField() const { - loadLazyMetadata(); - return _metadataFields->getSortKeyMetaField(); - } - void setSortKeyMetaField(BSONObj sortKey) { - loadLazyMetadata(); - _metadataFields->setSortKeyMetaField(sortKey); - } - - bool hasGeoNearDistance() const { loadLazyMetadata(); - return _metadataFields->hasGeoNearDistance(); - } - double getGeoNearDistance() const { - loadLazyMetadata(); - return _metadataFields->getGeoNearDistance(); - } - void setGeoNearDistance(double dist) { - loadLazyMetadata(); - _metadataFields->setGeoNearDistance(dist); - } - - bool hasGeoNearPoint() const { - loadLazyMetadata(); - return _metadataFields->hasGeoNearPoint(); - } - Value getGeoNearPoint() const { - loadLazyMetadata(); - return _metadataFields->getGeoNearPoint(); - } - void setGeoNearPoint(Value point) { - loadLazyMetadata(); - _metadataFields->setGeoNearPoint(point); + metadata().copyFrom(source.metadata()); } - bool hasSearchScore() const { - loadLazyMetadata(); - return _metadataFields->hasSearchScore(); - } - double getSearchScore() const { - loadLazyMetadata(); - return _metadataFields->getSearchScore(); - } - void setSearchScore(double score) { + /** + * Returns a const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + const DocumentMetadataFields& metadata() const { loadLazyMetadata(); - _metadataFields->setSearchScore(score); + return _metadataFields; } - bool hasSearchHighlights() const { - loadLazyMetadata(); - return _metadataFields->hasSearchHighlights(); - } - Value getSearchHighlights() const { - loadLazyMetadata(); - return _metadataFields->getSearchHighlights(); - } - void setSearchHighlights(Value highlights) { + /** + * Returns a non-const reference to an object housing the metadata fields associated with this + * WorkingSetMember. + */ + DocumentMetadataFields& metadata() { loadLazyMetadata(); - _metadataFields->setSearchHighlights(highlights); + return _metadataFields; } static unsigned hashKey(StringData name) { @@ -713,7 +511,7 @@ private: BSONObj _bson; mutable BSONObjIterator _bsonIt; - mutable std::unique_ptr<MetadataFields> _metadataFields; + mutable DocumentMetadataFields _metadataFields; // The storage constructed from a BSON value may contain metadata. When we process the BSON we // have to move the metadata to the MetadataFields object. If we know that the BSON does not diff --git a/src/mongo/db/pipeline/document_metadata_fields.cpp b/src/mongo/db/pipeline/document_metadata_fields.cpp new file mode 100644 index 00000000000..bfca826cb8d --- /dev/null +++ b/src/mongo/db/pipeline/document_metadata_fields.cpp @@ -0,0 +1,200 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/pipeline/document_metadata_fields.h" + +namespace mongo { + +DocumentMetadataFields::DocumentMetadataFields(const DocumentMetadataFields& other) + : _holder(other._holder ? std::make_unique<MetadataHolder>(*other._holder) : nullptr) {} + +DocumentMetadataFields& DocumentMetadataFields::operator=(const DocumentMetadataFields& other) { + _holder = other._holder ? std::make_unique<MetadataHolder>(*other._holder) : nullptr; + return *this; +} + +DocumentMetadataFields::DocumentMetadataFields(DocumentMetadataFields&& other) + : _holder(std::move(other._holder)) {} + +DocumentMetadataFields& DocumentMetadataFields::operator=(DocumentMetadataFields&& other) { + _holder = std::move(other._holder); + return *this; +} + +void DocumentMetadataFields::mergeWith(const DocumentMetadataFields& other) { + if (!hasTextScore() && other.hasTextScore()) { + setTextScore(other.getTextScore()); + } + if (!hasRandVal() && other.hasRandVal()) { + setRandVal(other.getRandVal()); + } + if (!hasSortKey() && other.hasSortKey()) { + setSortKey(other.getSortKey()); + } + if (!hasGeoNearDistance() && other.hasGeoNearDistance()) { + setGeoNearDistance(other.getGeoNearDistance()); + } + if (!hasGeoNearPoint() && other.hasGeoNearPoint()) { + setGeoNearPoint(other.getGeoNearPoint()); + } + if (!hasSearchScore() && other.hasSearchScore()) { + setSearchScore(other.getSearchScore()); + } + if (!hasSearchHighlights() && other.hasSearchHighlights()) { + setSearchHighlights(other.getSearchHighlights()); + } + if (!hasIndexKey() && other.hasIndexKey()) { + setIndexKey(other.getIndexKey()); + } +} + +void DocumentMetadataFields::copyFrom(const DocumentMetadataFields& other) { + if (other.hasTextScore()) { + setTextScore(other.getTextScore()); + } + if (other.hasRandVal()) { + setRandVal(other.getRandVal()); + } + if (other.hasSortKey()) { + setSortKey(other.getSortKey()); + } + if (other.hasGeoNearDistance()) { + setGeoNearDistance(other.getGeoNearDistance()); + } + if (other.hasGeoNearPoint()) { + setGeoNearPoint(other.getGeoNearPoint()); + } + if (other.hasSearchScore()) { + setSearchScore(other.getSearchScore()); + } + if (other.hasSearchHighlights()) { + setSearchHighlights(other.getSearchHighlights()); + } + if (other.hasIndexKey()) { + setIndexKey(other.getIndexKey()); + } +} + +size_t DocumentMetadataFields::getApproximateSize() const { + if (!_holder) { + return 0; + } + + // Purposefully exclude the size of the DocumentMetadataFields, as this is accounted for + // elsewhere. Here we only consider the "deep" size of the MetadataHolder. + size_t size = sizeof(MetadataHolder); + + // Count the "deep" portion of the metadata values. + size += _holder->sortKey.objsize(); + size += _holder->geoNearPoint.getApproximateSize(); + // Size of Value is double counted - once in sizeof(MetadataFields) and once in + // getApproximateSize() + size -= sizeof(_holder->geoNearPoint); + size += _holder->searchHighlights.getApproximateSize(); + size -= sizeof(_holder->searchHighlights); + size += _holder->indexKey.objsize(); + + return size; +} + +void DocumentMetadataFields::serializeForSorter(BufBuilder& buf) const { + // If there is no metadata, all we need to do is write a zero byte. + if (!_holder) { + buf.appendNum(static_cast<char>(0)); + return; + } + + if (hasTextScore()) { + buf.appendNum(static_cast<char>(MetaType::TEXT_SCORE + 1)); + buf.appendNum(getTextScore()); + } + if (hasRandVal()) { + buf.appendNum(static_cast<char>(MetaType::RAND_VAL + 1)); + buf.appendNum(getRandVal()); + } + if (hasSortKey()) { + buf.appendNum(static_cast<char>(MetaType::SORT_KEY + 1)); + getSortKey().appendSelfToBufBuilder(buf); + } + if (hasGeoNearDistance()) { + buf.appendNum(static_cast<char>(MetaType::GEONEAR_DIST + 1)); + buf.appendNum(getGeoNearDistance()); + } + if (hasGeoNearPoint()) { + buf.appendNum(static_cast<char>(MetaType::GEONEAR_POINT + 1)); + getGeoNearPoint().serializeForSorter(buf); + } + if (hasSearchScore()) { + buf.appendNum(static_cast<char>(MetaType::SEARCH_SCORE + 1)); + buf.appendNum(getSearchScore()); + } + if (hasSearchHighlights()) { + buf.appendNum(static_cast<char>(MetaType::SEARCH_HIGHLIGHTS + 1)); + getSearchHighlights().serializeForSorter(buf); + } + if (hasIndexKey()) { + buf.appendNum(static_cast<char>(MetaType::INDEX_KEY + 1)); + getIndexKey().appendSelfToBufBuilder(buf); + } + buf.appendNum(static_cast<char>(0)); +} + +void DocumentMetadataFields::deserializeForSorter(BufReader& buf, DocumentMetadataFields* out) { + invariant(out); + + while (char marker = buf.read<char>()) { + if (marker == static_cast<char>(MetaType::TEXT_SCORE) + 1) { + out->setTextScore(buf.read<LittleEndian<double>>()); + } else if (marker == static_cast<char>(MetaType::RAND_VAL) + 1) { + out->setRandVal(buf.read<LittleEndian<double>>()); + } else if (marker == static_cast<char>(MetaType::SORT_KEY) + 1) { + out->setSortKey( + BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings())); + } else if (marker == static_cast<char>(MetaType::GEONEAR_DIST) + 1) { + out->setGeoNearDistance(buf.read<LittleEndian<double>>()); + } else if (marker == static_cast<char>(MetaType::GEONEAR_POINT) + 1) { + out->setGeoNearPoint( + Value::deserializeForSorter(buf, Value::SorterDeserializeSettings())); + } else if (marker == static_cast<char>(MetaType::SEARCH_SCORE) + 1) { + out->setSearchScore(buf.read<LittleEndian<double>>()); + } else if (marker == static_cast<char>(MetaType::SEARCH_HIGHLIGHTS) + 1) { + out->setSearchHighlights( + Value::deserializeForSorter(buf, Value::SorterDeserializeSettings())); + } else if (marker == static_cast<char>(MetaType::INDEX_KEY) + 1) { + out->setIndexKey( + BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings())); + } else { + uasserted(28744, "Unrecognized marker, unable to deserialize buffer"); + } + } +} + +} // namespace mongo diff --git a/src/mongo/db/pipeline/document_metadata_fields.h b/src/mongo/db/pipeline/document_metadata_fields.h new file mode 100644 index 00000000000..95c11284580 --- /dev/null +++ b/src/mongo/db/pipeline/document_metadata_fields.h @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <bitset> + +#include "mongo/bson/bsonobj.h" +#include "mongo/db/pipeline/value.h" + +namespace mongo { + +/** + * This class represents the metadata that the query execution engine can associate with a + * particular intermediate result (either index key or document) passing between execution stages. + * + * Since most documents do not have metadata, this class can be left in an uninitialized state, in + * which case no memory is allocated to hold the metadata. The operator bool() overload can be used + * to determine whether or not a DocumentMetadataFields object is uninitialized (and thus has no + * metadata). + * + * Calling any of the setters is legal on an uninitialized object, and will cause the + * DocumentMetadataFields to transition to an initialized state. + * + * A DocumentMetadataFields is copy constructible, copy assignable, move constructible, and move + * assignable. + */ +class DocumentMetadataFields { +public: + enum MetaType : char { + TEXT_SCORE, + RAND_VAL, + SORT_KEY, + GEONEAR_DIST, + GEONEAR_POINT, + SEARCH_SCORE, + SEARCH_HIGHLIGHTS, + INDEX_KEY, + + // New fields must be added before the NUM_FIELDS sentinel. + NUM_FIELDS + }; + + /** + * Reads serialized metadata out of 'buf', and uses it to populate 'out'. Expects 'buf' to have + * been written to by a previous call to serializeForSorter(). It is illegal to pass a null + * pointer for 'out'. + */ + static void deserializeForSorter(BufReader& buf, DocumentMetadataFields* out); + + /** + * Constructs a new DocumentMetadataFields in an uninitialized state. + */ + DocumentMetadataFields() = default; + + DocumentMetadataFields(const DocumentMetadataFields& other); + DocumentMetadataFields& operator=(const DocumentMetadataFields& other); + + DocumentMetadataFields(DocumentMetadataFields&& other); + DocumentMetadataFields& operator=(DocumentMetadataFields&& other); + + /** + * For all metadata fields that 'other' has but 'this' does not have, copies these fields from + * 'other' to 'this'. + */ + void mergeWith(const DocumentMetadataFields& other); + + /** + * Copies all metadata fields that are present in 'other' from 'other' to 'this', overwriting + * values already present in 'this'. + * + * This differs slightly from the copy assignment operator. Copy-assignment will cause 'this' to + * equal 'other' exactly. This operation, on the other hand, leaves the metadata fields from + * 'this' which are not present in 'other' unmodified. + */ + void copyFrom(const DocumentMetadataFields& other); + + /** + * Returns an estimate in bytes of the size of the underlying metadata, which is held at a + * distance by this object. The size of this object is not incorporated in the estimate. + */ + size_t getApproximateSize() const; + + /** + * Returns true if this object is in an initialized state and may hold metadata. + */ + operator bool() const { + return static_cast<bool>(_holder); + } + + bool hasTextScore() const { + return _holder && _holder->metaFields.test(MetaType::TEXT_SCORE); + } + + double getTextScore() const { + invariant(hasTextScore()); + return _holder->textScore; + } + + void setTextScore(double score) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::TEXT_SCORE); + _holder->textScore = score; + } + + bool hasRandVal() const { + return _holder && _holder->metaFields.test(MetaType::RAND_VAL); + } + + double getRandVal() const { + invariant(hasRandVal()); + return _holder->randVal; + } + + void setRandVal(double val) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::RAND_VAL); + _holder->randVal = val; + } + + bool hasSortKey() const { + return _holder && _holder->metaFields.test(MetaType::SORT_KEY); + } + + BSONObj getSortKey() const { + invariant(hasSortKey()); + return _holder->sortKey; + } + + void setSortKey(BSONObj sortKey) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::SORT_KEY); + _holder->sortKey = sortKey.getOwned(); + } + + bool hasGeoNearDistance() const { + return _holder && _holder->metaFields.test(MetaType::GEONEAR_DIST); + } + + double getGeoNearDistance() const { + invariant(hasGeoNearDistance()); + return _holder->geoNearDistance; + } + + void setGeoNearDistance(double dist) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::GEONEAR_DIST); + _holder->geoNearDistance = dist; + } + + bool hasGeoNearPoint() const { + return _holder && _holder->metaFields.test(MetaType::GEONEAR_POINT); + } + + Value getGeoNearPoint() const { + invariant(hasGeoNearPoint()); + return _holder->geoNearPoint; + } + + void setGeoNearPoint(Value point) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::GEONEAR_POINT); + _holder->geoNearPoint = std::move(point); + } + + bool hasSearchScore() const { + return _holder && _holder->metaFields.test(MetaType::SEARCH_SCORE); + } + + double getSearchScore() const { + invariant(hasSearchScore()); + return _holder->searchScore; + } + + void setSearchScore(double score) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::SEARCH_SCORE); + _holder->searchScore = score; + } + + bool hasSearchHighlights() const { + return _holder && _holder->metaFields.test(MetaType::SEARCH_HIGHLIGHTS); + } + + Value getSearchHighlights() const { + invariant(hasSearchHighlights()); + return _holder->searchHighlights; + } + + void setSearchHighlights(Value highlights) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::SEARCH_HIGHLIGHTS); + _holder->searchHighlights = highlights; + } + + bool hasIndexKey() const { + return _holder && _holder->metaFields.test(MetaType::INDEX_KEY); + } + + BSONObj getIndexKey() const { + invariant(hasIndexKey()); + return _holder->indexKey; + } + + void setIndexKey(BSONObj indexKey) { + if (!_holder) { + _holder = std::make_unique<MetadataHolder>(); + } + + _holder->metaFields.set(MetaType::INDEX_KEY); + _holder->indexKey = indexKey.getOwned(); + } + + void serializeForSorter(BufBuilder& buf) const; + +private: + // A simple data struct housing all possible metadata fields. + struct MetadataHolder { + std::bitset<MetaType::NUM_FIELDS> metaFields; + double textScore{0.0}; + double randVal{0.0}; + BSONObj sortKey; + double geoNearDistance{0.0}; + Value geoNearPoint; + double searchScore{0.0}; + Value searchHighlights; + BSONObj indexKey; + }; + + // Null until the first setter is called, at which point a MetadataHolder struct is allocated. + std::unique_ptr<MetadataHolder> _holder; +}; + +} // namespace mongo diff --git a/src/mongo/db/pipeline/document_metadata_fields_test.cpp b/src/mongo/db/pipeline/document_metadata_fields_test.cpp new file mode 100644 index 00000000000..5e46ea7de7f --- /dev/null +++ b/src/mongo/db/pipeline/document_metadata_fields_test.cpp @@ -0,0 +1,242 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/pipeline/document_metadata_fields.h" +#include "mongo/db/pipeline/document_value_test_util.h" +#include "mongo/unittest/bson_test_util.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +TEST(DocumentMetadataFieldsTest, AllMetadataRoundtripsThroughSerialization) { + DocumentMetadataFields metadata; + metadata.setTextScore(9.9); + metadata.setRandVal(42.0); + metadata.setSortKey(BSON("a" << 1)); + metadata.setGeoNearDistance(3.2); + metadata.setGeoNearPoint(Value{BSON_ARRAY(1 << 2)}); + metadata.setSearchScore(5.4); + metadata.setSearchHighlights(Value{"foo"_sd}); + metadata.setIndexKey(BSON("b" << 1)); + + BufBuilder builder; + metadata.serializeForSorter(builder); + DocumentMetadataFields deserialized; + BufReader reader(builder.buf(), builder.len()); + DocumentMetadataFields::deserializeForSorter(reader, &deserialized); + + ASSERT_EQ(deserialized.getTextScore(), 9.9); + ASSERT_EQ(deserialized.getRandVal(), 42.0); + ASSERT_BSONOBJ_EQ(deserialized.getSortKey(), BSON("a" << 1)); + ASSERT_EQ(deserialized.getGeoNearDistance(), 3.2); + ASSERT_VALUE_EQ(deserialized.getGeoNearPoint(), Value{BSON_ARRAY(1 << 2)}); + ASSERT_EQ(deserialized.getSearchScore(), 5.4); + ASSERT_VALUE_EQ(deserialized.getSearchHighlights(), Value{"foo"_sd}); + ASSERT_BSONOBJ_EQ(deserialized.getIndexKey(), BSON("b" << 1)); +} + +TEST(DocumentMetadataFieldsTest, HasMethodsReturnFalseForEmptyMetadata) { + DocumentMetadataFields metadata; + ASSERT_FALSE(metadata); + ASSERT_FALSE(metadata.hasTextScore()); + ASSERT_FALSE(metadata.hasRandVal()); + ASSERT_FALSE(metadata.hasSortKey()); + ASSERT_FALSE(metadata.hasGeoNearPoint()); + ASSERT_FALSE(metadata.hasGeoNearDistance()); + ASSERT_FALSE(metadata.hasSearchScore()); + ASSERT_FALSE(metadata.hasSearchHighlights()); + ASSERT_FALSE(metadata.hasIndexKey()); +} + +TEST(DocumentMetadataFieldsTest, HasMethodsReturnTrueForInitializedMetadata) { + DocumentMetadataFields metadata; + + ASSERT_FALSE(metadata.hasTextScore()); + metadata.setTextScore(9.9); + ASSERT_TRUE(metadata.hasTextScore()); + + ASSERT_FALSE(metadata.hasRandVal()); + metadata.setRandVal(42.0); + ASSERT_TRUE(metadata.hasRandVal()); + + ASSERT_FALSE(metadata.hasSortKey()); + metadata.setSortKey(BSON("a" << 1)); + ASSERT_TRUE(metadata.hasSortKey()); + + ASSERT_FALSE(metadata.hasGeoNearDistance()); + metadata.setGeoNearDistance(3.2); + ASSERT_TRUE(metadata.hasGeoNearDistance()); + + ASSERT_FALSE(metadata.hasGeoNearPoint()); + metadata.setGeoNearPoint(Value{BSON_ARRAY(1 << 2)}); + ASSERT_TRUE(metadata.hasGeoNearPoint()); + + ASSERT_FALSE(metadata.hasSearchScore()); + metadata.setSearchScore(5.4); + ASSERT_TRUE(metadata.hasSearchScore()); + + ASSERT_FALSE(metadata.hasSearchHighlights()); + metadata.setSearchHighlights(Value{"foo"_sd}); + ASSERT_TRUE(metadata.hasSearchHighlights()); + + ASSERT_FALSE(metadata.hasIndexKey()); + metadata.setIndexKey(BSON("b" << 1)); + ASSERT_TRUE(metadata.hasIndexKey()); +} + +TEST(DocumentMetadataFieldsTest, MoveConstructor) { + DocumentMetadataFields metadata; + metadata.setTextScore(9.9); + metadata.setRandVal(42.0); + metadata.setSortKey(BSON("a" << 1)); + metadata.setGeoNearDistance(3.2); + metadata.setGeoNearPoint(Value{BSON_ARRAY(1 << 2)}); + metadata.setSearchScore(5.4); + metadata.setSearchHighlights(Value{"foo"_sd}); + metadata.setIndexKey(BSON("b" << 1)); + + DocumentMetadataFields moveConstructed(std::move(metadata)); + ASSERT_TRUE(moveConstructed); + ASSERT_EQ(moveConstructed.getTextScore(), 9.9); + ASSERT_EQ(moveConstructed.getRandVal(), 42.0); + ASSERT_BSONOBJ_EQ(moveConstructed.getSortKey(), BSON("a" << 1)); + ASSERT_EQ(moveConstructed.getGeoNearDistance(), 3.2); + ASSERT_VALUE_EQ(moveConstructed.getGeoNearPoint(), Value{BSON_ARRAY(1 << 2)}); + ASSERT_EQ(moveConstructed.getSearchScore(), 5.4); + ASSERT_VALUE_EQ(moveConstructed.getSearchHighlights(), Value{"foo"_sd}); + ASSERT_BSONOBJ_EQ(moveConstructed.getIndexKey(), BSON("b" << 1)); + + ASSERT_FALSE(metadata); +} + +TEST(DocumentMetadataFieldsTest, MoveAssignmentOperator) { + DocumentMetadataFields metadata; + metadata.setTextScore(9.9); + metadata.setRandVal(42.0); + metadata.setSortKey(BSON("a" << 1)); + metadata.setGeoNearDistance(3.2); + metadata.setGeoNearPoint(Value{BSON_ARRAY(1 << 2)}); + metadata.setSearchScore(5.4); + metadata.setSearchHighlights(Value{"foo"_sd}); + metadata.setIndexKey(BSON("b" << 1)); + + DocumentMetadataFields moveAssigned; + moveAssigned.setTextScore(12.3); + moveAssigned = std::move(metadata); + ASSERT_TRUE(moveAssigned); + + ASSERT_EQ(moveAssigned.getTextScore(), 9.9); + ASSERT_EQ(moveAssigned.getRandVal(), 42.0); + ASSERT_BSONOBJ_EQ(moveAssigned.getSortKey(), BSON("a" << 1)); + ASSERT_EQ(moveAssigned.getGeoNearDistance(), 3.2); + ASSERT_VALUE_EQ(moveAssigned.getGeoNearPoint(), Value{BSON_ARRAY(1 << 2)}); + ASSERT_EQ(moveAssigned.getSearchScore(), 5.4); + ASSERT_VALUE_EQ(moveAssigned.getSearchHighlights(), Value{"foo"_sd}); + ASSERT_BSONOBJ_EQ(moveAssigned.getIndexKey(), BSON("b" << 1)); + + ASSERT_FALSE(metadata); +} + +TEST(DocumentMetadataFieldsTest, CopyConstructor) { + DocumentMetadataFields metadata; + metadata.setTextScore(9.9); + + DocumentMetadataFields copied{metadata}; + + ASSERT_TRUE(metadata); + ASSERT_TRUE(copied); + ASSERT_EQ(metadata.getTextScore(), 9.9); + ASSERT_EQ(copied.getTextScore(), 9.9); +} + +TEST(DocumentMetadataFieldsTest, CopyAssignmentOperator) { + DocumentMetadataFields metadata; + metadata.setTextScore(9.9); + + DocumentMetadataFields copied; + copied.setTextScore(12.3); + copied = metadata; + + ASSERT_TRUE(metadata); + ASSERT_TRUE(copied); + ASSERT_EQ(metadata.getTextScore(), 9.9); + ASSERT_EQ(copied.getTextScore(), 9.9); +} + +TEST(DocumentMetadataFieldsTest, MergeWithOnlyCopiesMetadataThatDestinationDoesNotHave) { + DocumentMetadataFields source; + source.setTextScore(9.9); + source.setRandVal(42.0); + source.setSortKey(BSON("a" << 1)); + source.setGeoNearDistance(3.2); + + DocumentMetadataFields destination; + destination.setTextScore(12.3); + destination.setRandVal(84.0); + + destination.mergeWith(source); + + ASSERT_EQ(destination.getTextScore(), 12.3); + ASSERT_EQ(destination.getRandVal(), 84.0); + ASSERT_BSONOBJ_EQ(destination.getSortKey(), BSON("a" << 1)); + ASSERT_EQ(destination.getGeoNearDistance(), 3.2); + ASSERT_FALSE(destination.hasGeoNearPoint()); + ASSERT_FALSE(destination.hasSearchScore()); + ASSERT_FALSE(destination.hasSearchHighlights()); + ASSERT_FALSE(destination.hasIndexKey()); +} + +TEST(DocumentMetadataFieldsTest, CopyFromCopiesAllMetadataThatSourceHas) { + DocumentMetadataFields source; + source.setTextScore(9.9); + source.setRandVal(42.0); + source.setSortKey(BSON("a" << 1)); + source.setGeoNearDistance(3.2); + + DocumentMetadataFields destination; + destination.setTextScore(12.3); + destination.setRandVal(84.0); + + destination.copyFrom(source); + + ASSERT_EQ(destination.getTextScore(), 9.9); + ASSERT_EQ(destination.getRandVal(), 42.0); + ASSERT_BSONOBJ_EQ(destination.getSortKey(), BSON("a" << 1)); + ASSERT_EQ(destination.getGeoNearDistance(), 3.2); + ASSERT_FALSE(destination.hasGeoNearPoint()); + ASSERT_FALSE(destination.hasSearchScore()); + ASSERT_FALSE(destination.hasSearchHighlights()); + ASSERT_FALSE(destination.hasIndexKey()); +} + +} // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_change_stream_test.cpp b/src/mongo/db/pipeline/document_source_change_stream_test.cpp index f50808b97e9..65a2ed55821 100644 --- a/src/mongo/db/pipeline/document_source_change_stream_test.cpp +++ b/src/mongo/db/pipeline/document_source_change_stream_test.cpp @@ -1689,7 +1689,7 @@ TEST_F(ChangeStreamStageTest, UsesResumeTokenAsSortKeyIfNeedsMergeIsFalse) { makeResumeToken(kDefaultTs, testUuid(), BSON("x" << 2 << "_id" << 1)).toBson(); ASSERT_TRUE(next.isAdvanced()); - ASSERT_BSONOBJ_EQ(next.releaseDocument().getSortKeyMetaField(), expectedSortKey); + ASSERT_BSONOBJ_EQ(next.releaseDocument().metadata().getSortKey(), expectedSortKey); } // diff --git a/src/mongo/db/pipeline/document_source_change_stream_transform.cpp b/src/mongo/db/pipeline/document_source_change_stream_transform.cpp index 2da744ae88e..1c6739b8497 100644 --- a/src/mongo/db/pipeline/document_source_change_stream_transform.cpp +++ b/src/mongo/db/pipeline/document_source_change_stream_transform.cpp @@ -328,7 +328,7 @@ Document DocumentSourceChangeStreamTransform::applyTransformation(const Document // We set the resume token as the document's sort key in both the sharded and non-sharded cases, // since we will subsequently rely upon it to generate a correct postBatchResumeToken. - doc.setSortKeyMetaField(resumeToken.toBson()); + doc.metadata().setSortKey(resumeToken.toBson()); // "invalidate" and "newShardDetected" entries have fewer fields. if (operationType == DocumentSourceChangeStream::kInvalidateOpType || diff --git a/src/mongo/db/pipeline/document_source_check_invalidate.cpp b/src/mongo/db/pipeline/document_source_check_invalidate.cpp index 276f7edb5b9..376e0dc97c9 100644 --- a/src/mongo/db/pipeline/document_source_check_invalidate.cpp +++ b/src/mongo/db/pipeline/document_source_check_invalidate.cpp @@ -107,7 +107,7 @@ DocumentSource::GetNextResult DocumentSourceCheckInvalidate::getNext() { // We set the resume token as the document's sort key in both the sharded and non-sharded // cases, since we will later rely upon it to generate a correct postBatchResumeToken. We // must therefore update the sort key to match the new resume token that we generated above. - result.setSortKeyMetaField(resumeTokenDoc.toBson()); + result.metadata().setSortKey(resumeTokenDoc.toBson()); _queuedInvalidate = result.freeze(); } diff --git a/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp b/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp index 0a2d4b71e45..6a369e84a69 100644 --- a/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp +++ b/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp @@ -88,25 +88,25 @@ Document DocumentSourceGeoNearCursor::transformBSONObjToDocument(const BSONObj& MutableDocument output(Document::fromBsonWithMetaData(obj)); // Scale the distance by the requested factor. - invariant(output.peek().hasGeoNearDistance(), + invariant(output.peek().metadata().hasGeoNearDistance(), str::stream() << "Query returned a document that is unexpectedly missing the geoNear distance: " << obj.jsonString()); - const auto distance = output.peek().getGeoNearDistance() * _distanceMultiplier; + const auto distance = output.peek().metadata().getGeoNearDistance() * _distanceMultiplier; output.setNestedField(_distanceField, Value(distance)); if (_locationField) { invariant( - output.peek().hasGeoNearPoint(), + output.peek().metadata().hasGeoNearPoint(), str::stream() << "Query returned a document that is unexpectedly missing the geoNear point: " << obj.jsonString()); - output.setNestedField(*_locationField, output.peek().getGeoNearPoint()); + output.setNestedField(*_locationField, output.peek().metadata().getGeoNearPoint()); } // In a cluster, $geoNear will be merged via $sort, so add the sort key. if (pExpCtx->needsMerge) { - output.setSortKeyMetaField(BSON("" << distance)); + output.metadata().setSortKey(BSON("" << distance)); } return output.freeze(); diff --git a/src/mongo/db/pipeline/document_source_sample.cpp b/src/mongo/db/pipeline/document_source_sample.cpp index 23a71562a30..072b7a22d1c 100644 --- a/src/mongo/db/pipeline/document_source_sample.cpp +++ b/src/mongo/db/pipeline/document_source_sample.cpp @@ -62,7 +62,7 @@ DocumentSource::GetNextResult DocumentSourceSample::getNext() { auto nextInput = pSource->getNext(); for (; nextInput.isAdvanced(); nextInput = pSource->getNext()) { MutableDocument doc(nextInput.releaseDocument()); - doc.setRandMetaField(prng.nextCanonicalDouble()); + doc.metadata().setRandVal(prng.nextCanonicalDouble()); _sortStage->loadDocument(doc.freeze()); } switch (nextInput.getStatus()) { diff --git a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp index ad84c24e9aa..a3a33ca6f4c 100644 --- a/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp +++ b/src/mongo/db/pipeline/document_source_sample_from_random_cursor.cpp @@ -92,11 +92,11 @@ DocumentSource::GetNextResult DocumentSourceSampleFromRandomCursor::getNext() { _randMetaFieldVal -= smallestFromSampleOfUniform(&prng, _nDocsInColl); MutableDocument md(nextResult.releaseDocument()); - md.setRandMetaField(_randMetaFieldVal); + md.metadata().setRandVal(_randMetaFieldVal); if (pExpCtx->needsMerge) { // This stage will be merged by sorting results according to this random metadata field, but // the merging logic expects to sort by the sort key metadata. - md.setSortKeyMetaField(BSON("" << _randMetaFieldVal)); + md.metadata().setSortKey(BSON("" << _randMetaFieldVal)); } return md.freeze(); } diff --git a/src/mongo/db/pipeline/document_source_sample_test.cpp b/src/mongo/db/pipeline/document_source_sample_test.cpp index 039456ac440..0d38fa914a9 100644 --- a/src/mongo/db/pipeline/document_source_sample_test.cpp +++ b/src/mongo/db/pipeline/document_source_sample_test.cpp @@ -87,9 +87,9 @@ protected: auto nextResult = sample()->getNext(); ASSERT_TRUE(nextResult.isAdvanced()); auto thisDoc = nextResult.releaseDocument(); - ASSERT_TRUE(thisDoc.hasRandMetaField()); + ASSERT_TRUE(thisDoc.metadata().hasRandVal()); if (prevDoc) { - ASSERT_LTE(thisDoc.getRandMetaField(), prevDoc->getRandMetaField()); + ASSERT_LTE(thisDoc.metadata().getRandVal(), prevDoc->metadata().getRandVal()); } prevDoc = std::move(thisDoc); } @@ -165,7 +165,7 @@ TEST_F(SampleBasics, DocsUnmodified) { auto doc = next.releaseDocument(); ASSERT_EQUALS(1, doc["a"].getInt()); ASSERT_EQUALS(2, doc["b"]["c"].getInt()); - ASSERT_TRUE(doc.hasRandMetaField()); + ASSERT_TRUE(doc.metadata().hasRandVal()); assertEOF(); } @@ -281,7 +281,7 @@ TEST_F(SampleFromRandomCursorBasics, DocsUnmodified) { auto doc = next.releaseDocument(); ASSERT_EQUALS(1, doc["_id"].getInt()); ASSERT_EQUALS(2, doc["b"]["c"].getInt()); - ASSERT_TRUE(doc.hasRandMetaField()); + ASSERT_TRUE(doc.metadata().hasRandVal()); assertEOF(); } @@ -298,16 +298,16 @@ TEST_F(SampleFromRandomCursorBasics, IgnoreDuplicates) { ASSERT_TRUE(next.isAdvanced()); auto doc = next.releaseDocument(); ASSERT_EQUALS(1, doc["_id"].getInt()); - ASSERT_TRUE(doc.hasRandMetaField()); - double doc1Meta = doc.getRandMetaField(); + ASSERT_TRUE(doc.metadata().hasRandVal()); + double doc1Meta = doc.metadata().getRandVal(); // Should ignore the duplicate {_id: 1}, and return {_id: 2}. next = sample()->getNext(); ASSERT_TRUE(next.isAdvanced()); doc = next.releaseDocument(); ASSERT_EQUALS(2, doc["_id"].getInt()); - ASSERT_TRUE(doc.hasRandMetaField()); - double doc2Meta = doc.getRandMetaField(); + ASSERT_TRUE(doc.metadata().hasRandVal()); + double doc2Meta = doc.metadata().getRandVal(); ASSERT_GTE(doc1Meta, doc2Meta); // Both stages should be exhausted. @@ -371,13 +371,13 @@ TEST_F(SampleFromRandomCursorBasics, MimicNonOptimized) { auto doc = sample()->getNext(); ASSERT_TRUE(doc.isAdvanced()); - ASSERT_TRUE(doc.getDocument().hasRandMetaField()); - firstTotal += doc.getDocument().getRandMetaField(); + ASSERT_TRUE(doc.getDocument().metadata().hasRandVal()); + firstTotal += doc.getDocument().metadata().getRandVal(); doc = sample()->getNext(); ASSERT_TRUE(doc.isAdvanced()); - ASSERT_TRUE(doc.getDocument().hasRandMetaField()); - secondTotal += doc.getDocument().getRandMetaField(); + ASSERT_TRUE(doc.getDocument().metadata().hasRandVal()); + secondTotal += doc.getDocument().metadata().getRandVal(); } // The average random meta value of the first document should be about 0.75. We assume that // 10000 trials is sufficient for us to apply the Central Limit Theorem. Using an error diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp index 65f3d3bf8a4..dda569e4350 100644 --- a/src/mongo/db/pipeline/document_source_sort.cpp +++ b/src/mongo/db/pipeline/document_source_sort.cpp @@ -337,11 +337,11 @@ StatusWith<Value> DocumentSourceSort::extractKeyFast(const Document& doc) const BSONObj DocumentSourceSort::extractKeyWithArray(const Document& doc) const { SortKeyGenerator::Metadata metadata; - if (doc.hasTextScore()) { - metadata.textScore = doc.getTextScore(); + if (doc.metadata().hasTextScore()) { + metadata.textScore = doc.metadata().getTextScore(); } - if (doc.hasRandMetaField()) { - metadata.randVal = doc.getRandMetaField(); + if (doc.metadata().hasRandVal()) { + metadata.randVal = doc.metadata().getRandVal(); } // Convert the Document to a BSONObj, but only do the conversion for the paths we actually need. @@ -380,7 +380,7 @@ std::pair<Value, Document> DocumentSourceSort::extractSortKey(Document&& doc) co // We need to be merged, so will have to be serialized. Save the sort key here to avoid // re-computing it during the merge. invariant(serializedSortKey); - toBeSorted.setSortKeyMetaField(*serializedSortKey); + toBeSorted.metadata().setSortKey(*serializedSortKey); } return {inMemorySortKey, toBeSorted.freeze()}; } diff --git a/src/mongo/db/pipeline/document_source_sort_test.cpp b/src/mongo/db/pipeline/document_source_sort_test.cpp index 00a4dad3c70..fe4275d310f 100644 --- a/src/mongo/db/pipeline/document_source_sort_test.cpp +++ b/src/mongo/db/pipeline/document_source_sort_test.cpp @@ -309,9 +309,9 @@ TEST_F(DocumentSourceSortExecutionTest, NullValue) { */ TEST_F(DocumentSourceSortExecutionTest, TextScore) { MutableDocument first(Document{{"_id", 0}}); - first.setTextScore(10); + first.metadata().setTextScore(10); MutableDocument second(Document{{"_id", 1}}); - second.setTextScore(20); + second.metadata().setTextScore(20); checkResults({first.freeze(), second.freeze()}, BSON("$computed0" << metaTextScore), @@ -323,9 +323,9 @@ TEST_F(DocumentSourceSortExecutionTest, TextScore) { */ TEST_F(DocumentSourceSortExecutionTest, RandMeta) { MutableDocument first(Document{{"_id", 0}}); - first.setRandMetaField(0.01); + first.metadata().setRandVal(0.01); MutableDocument second(Document{{"_id", 1}}); - second.setRandMetaField(0.02); + second.metadata().setRandVal(0.02); checkResults({first.freeze(), second.freeze()}, BSON("$computed0" << BSON("$meta" diff --git a/src/mongo/db/pipeline/document_value_test.cpp b/src/mongo/db/pipeline/document_value_test.cpp index e651c840fe9..67d669403da 100644 --- a/src/mongo/db/pipeline/document_value_test.cpp +++ b/src/mongo/db/pipeline/document_value_test.cpp @@ -500,79 +500,139 @@ namespace MetaFields { using mongo::Document; TEST(MetaFields, TextScoreBasics) { // Documents should not have a text score until it is set. - ASSERT_FALSE(Document().hasTextScore()); + ASSERT_FALSE(Document().metadata().hasTextScore()); // Setting the text score should work as expected. MutableDocument docBuilder; - docBuilder.setTextScore(1.0); + docBuilder.metadata().setTextScore(1.0); Document doc = docBuilder.freeze(); - ASSERT_TRUE(doc.hasTextScore()); - ASSERT_EQ(1.0, doc.getTextScore()); + ASSERT_TRUE(doc.metadata().hasTextScore()); + ASSERT_EQ(1.0, doc.metadata().getTextScore()); } TEST(MetaFields, RandValBasics) { // Documents should not have a random value until it is set. - ASSERT_FALSE(Document().hasRandMetaField()); + ASSERT_FALSE(Document().metadata().hasRandVal()); // Setting the random value field should work as expected. MutableDocument docBuilder; - docBuilder.setRandMetaField(1.0); + docBuilder.metadata().setRandVal(1.0); Document doc = docBuilder.freeze(); - ASSERT_TRUE(doc.hasRandMetaField()); - ASSERT_EQ(1, doc.getRandMetaField()); + ASSERT_TRUE(doc.metadata().hasRandVal()); + ASSERT_EQ(1, doc.metadata().getRandVal()); // Setting the random value twice should keep the second value. MutableDocument docBuilder2; - docBuilder2.setRandMetaField(1.0); - docBuilder2.setRandMetaField(2.0); + docBuilder2.metadata().setRandVal(1.0); + docBuilder2.metadata().setRandVal(2.0); Document doc2 = docBuilder2.freeze(); - ASSERT_TRUE(doc2.hasRandMetaField()); - ASSERT_EQ(2.0, doc2.getRandMetaField()); + ASSERT_TRUE(doc2.metadata().hasRandVal()); + ASSERT_EQ(2.0, doc2.metadata().getRandVal()); } TEST(MetaFields, SearchScoreBasic) { // Documents should not have a search score until it is set. - ASSERT_FALSE(Document().hasSearchScore()); + ASSERT_FALSE(Document().metadata().hasSearchScore()); // Setting the search score field should work as expected. MutableDocument docBuilder; - docBuilder.setSearchScore(1.23); + docBuilder.metadata().setSearchScore(1.23); Document doc = docBuilder.freeze(); - ASSERT_TRUE(doc.hasSearchScore()); - ASSERT_EQ(1.23, doc.getSearchScore()); + ASSERT_TRUE(doc.metadata().hasSearchScore()); + ASSERT_EQ(1.23, doc.metadata().getSearchScore()); // Setting the searchScore twice should keep the second value. MutableDocument docBuilder2; - docBuilder2.setSearchScore(1.0); - docBuilder2.setSearchScore(2.0); + docBuilder2.metadata().setSearchScore(1.0); + docBuilder2.metadata().setSearchScore(2.0); Document doc2 = docBuilder2.freeze(); - ASSERT_TRUE(doc2.hasSearchScore()); - ASSERT_EQ(2.0, doc2.getSearchScore()); + ASSERT_TRUE(doc2.metadata().hasSearchScore()); + ASSERT_EQ(2.0, doc2.metadata().getSearchScore()); } TEST(MetaFields, SearchHighlightsBasic) { // Documents should not have a search highlights until it is set. - ASSERT_FALSE(Document().hasSearchHighlights()); + ASSERT_FALSE(Document().metadata().hasSearchHighlights()); // Setting the search highlights field should work as expected. MutableDocument docBuilder; Value highlights = DOC_ARRAY("a"_sd << "b"_sd); - docBuilder.setSearchHighlights(highlights); + docBuilder.metadata().setSearchHighlights(highlights); Document doc = docBuilder.freeze(); - ASSERT_TRUE(doc.hasSearchHighlights()); - ASSERT_VALUE_EQ(doc.getSearchHighlights(), highlights); + ASSERT_TRUE(doc.metadata().hasSearchHighlights()); + ASSERT_VALUE_EQ(doc.metadata().getSearchHighlights(), highlights); // Setting the searchHighlights twice should keep the second value. MutableDocument docBuilder2; Value otherHighlights = DOC_ARRAY("snippet1"_sd << "snippet2"_sd << "snippet3"_sd); - docBuilder2.setSearchHighlights(highlights); - docBuilder2.setSearchHighlights(otherHighlights); + docBuilder2.metadata().setSearchHighlights(highlights); + docBuilder2.metadata().setSearchHighlights(otherHighlights); Document doc2 = docBuilder2.freeze(); - ASSERT_TRUE(doc2.hasSearchHighlights()); - ASSERT_VALUE_EQ(doc2.getSearchHighlights(), otherHighlights); + ASSERT_TRUE(doc2.metadata().hasSearchHighlights()); + ASSERT_VALUE_EQ(doc2.metadata().getSearchHighlights(), otherHighlights); +} + +TEST(MetaFields, IndexKeyMetadataSerializesCorrectly) { + Document doc{BSON("a" << 1)}; + MutableDocument mutableDoc{doc}; + mutableDoc.metadata().setIndexKey(BSON("b" << 1)); + doc = mutableDoc.freeze(); + + ASSERT_TRUE(doc.metadata().hasIndexKey()); + ASSERT_BSONOBJ_EQ(doc.metadata().getIndexKey(), BSON("b" << 1)); + + auto serialized = doc.toBsonWithMetaData(); + ASSERT_BSONOBJ_EQ(serialized, BSON("a" << 1 << "$indexKey" << BSON("b" << 1))); +} + +TEST(MetaFields, FromBsonWithMetadataAcceptsIndexKeyMetadata) { + auto doc = Document::fromBsonWithMetaData(BSON("a" << 1 << "$indexKey" << BSON("b" << 1))); + ASSERT_TRUE(doc.metadata().hasIndexKey()); + ASSERT_BSONOBJ_EQ(doc.metadata().getIndexKey(), BSON("b" << 1)); + auto bsonWithoutMetadata = doc.toBson(); + ASSERT_BSONOBJ_EQ(bsonWithoutMetadata, BSON("a" << 1)); +} + +TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) { + Document source = Document::fromBsonWithMetaData(BSON( + "a" << 1 << "$textScore" << 9.9 << "b" << 1 << "$randVal" << 42.0 << "c" << 1 << "$sortKey" + << BSON("x" << 1) + << "d" + << 1 + << "$dis" + << 3.2 + << "e" + << 1 + << "$pt" + << BSON_ARRAY(1 << 2) + << "f" + << 1 + << "$searchScore" + << 5.4 + << "g" + << 1 + << "$searchHighlights" + << "foo" + << "h" + << 1 + << "$indexKey" + << BSON("y" << 1))); + + MutableDocument destination{}; + destination.copyMetaDataFrom(source); + auto result = destination.freeze(); + + ASSERT_EQ(result.metadata().getTextScore(), 9.9); + ASSERT_EQ(result.metadata().getRandVal(), 42.0); + ASSERT_BSONOBJ_EQ(result.metadata().getSortKey(), BSON("x" << 1)); + ASSERT_EQ(result.metadata().getGeoNearDistance(), 3.2); + ASSERT_VALUE_EQ(result.metadata().getGeoNearPoint(), Value{BSON_ARRAY(1 << 2)}); + 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)); } class SerializationTest : public unittest::Test { @@ -588,18 +648,22 @@ protected: // Round trip to/from a buffer. auto output = roundTrip(input); ASSERT_DOCUMENT_EQ(output, input); - ASSERT_EQ(output.hasTextScore(), input.hasTextScore()); - ASSERT_EQ(output.hasRandMetaField(), input.hasRandMetaField()); - ASSERT_EQ(output.hasSearchScore(), input.hasSearchScore()); - ASSERT_EQ(output.hasSearchHighlights(), input.hasSearchHighlights()); - if (input.hasTextScore()) - ASSERT_EQ(output.getTextScore(), input.getTextScore()); - if (input.hasRandMetaField()) - ASSERT_EQ(output.getRandMetaField(), input.getRandMetaField()); - if (input.hasSearchScore()) - ASSERT_EQ(output.getSearchScore(), input.getSearchScore()); - if (input.hasSearchHighlights()) - ASSERT_VALUE_EQ(output.getSearchHighlights(), input.getSearchHighlights()); + ASSERT_EQ(output.metadata().hasTextScore(), input.metadata().hasTextScore()); + ASSERT_EQ(output.metadata().hasRandVal(), input.metadata().hasRandVal()); + ASSERT_EQ(output.metadata().hasSearchScore(), input.metadata().hasSearchScore()); + ASSERT_EQ(output.metadata().hasSearchHighlights(), input.metadata().hasSearchHighlights()); + ASSERT_EQ(output.metadata().hasIndexKey(), input.metadata().hasIndexKey()); + if (input.metadata().hasTextScore()) + ASSERT_EQ(output.metadata().getTextScore(), input.metadata().getTextScore()); + if (input.metadata().hasRandVal()) + ASSERT_EQ(output.metadata().getRandVal(), input.metadata().getRandVal()); + if (input.metadata().hasSearchScore()) + ASSERT_EQ(output.metadata().getSearchScore(), input.metadata().getSearchScore()); + if (input.metadata().hasSearchHighlights()) + ASSERT_VALUE_EQ(output.metadata().getSearchHighlights(), + input.metadata().getSearchHighlights()); + if (input.metadata().hasIndexKey()) + ASSERT_BSONOBJ_EQ(output.metadata().getIndexKey(), input.metadata().getIndexKey()); ASSERT(output.toBson().binaryEqual(input.toBson())); } @@ -607,42 +671,43 @@ protected: TEST_F(SerializationTest, MetaSerializationNoVals) { MutableDocument docBuilder; - docBuilder.setTextScore(10.0); - docBuilder.setRandMetaField(20.0); - docBuilder.setSearchScore(30.0); - docBuilder.setSearchHighlights(DOC_ARRAY("abc"_sd - << "def"_sd)); + docBuilder.metadata().setTextScore(10.0); + docBuilder.metadata().setRandVal(20.0); + docBuilder.metadata().setSearchScore(30.0); + docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd + << "def"_sd)); assertRoundTrips(docBuilder.freeze()); } TEST_F(SerializationTest, MetaSerializationWithVals) { // Same as above test, but add a non-meta field as well. MutableDocument docBuilder(DOC("foo" << 10)); - docBuilder.setTextScore(10.0); - docBuilder.setRandMetaField(20.0); - docBuilder.setSearchScore(30.0); - docBuilder.setSearchHighlights(DOC_ARRAY("abc"_sd - << "def"_sd)); + docBuilder.metadata().setTextScore(10.0); + docBuilder.metadata().setRandVal(20.0); + docBuilder.metadata().setSearchScore(30.0); + docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd + << "def"_sd)); + docBuilder.metadata().setIndexKey(BSON("key" << 42)); assertRoundTrips(docBuilder.freeze()); } TEST_F(SerializationTest, MetaSerializationSearchHighlightsNonArray) { MutableDocument docBuilder; - docBuilder.setTextScore(10.0); - docBuilder.setRandMetaField(20.0); - docBuilder.setSearchScore(30.0); + docBuilder.metadata().setTextScore(10.0); + docBuilder.metadata().setRandVal(20.0); + docBuilder.metadata().setSearchScore(30.0); // Everything should still round trip even if the searchHighlights metadata isn't an array. - docBuilder.setSearchHighlights(Value(1.23)); + docBuilder.metadata().setSearchHighlights(Value(1.23)); assertRoundTrips(docBuilder.freeze()); } TEST(MetaFields, ToAndFromBson) { MutableDocument docBuilder; - docBuilder.setTextScore(10.0); - docBuilder.setRandMetaField(20.0); - docBuilder.setSearchScore(30.0); - docBuilder.setSearchHighlights(DOC_ARRAY("abc"_sd - << "def"_sd)); + docBuilder.metadata().setTextScore(10.0); + docBuilder.metadata().setRandVal(20.0); + docBuilder.metadata().setSearchScore(30.0); + docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd + << "def"_sd)); Document doc = docBuilder.freeze(); BSONObj obj = doc.toBsonWithMetaData(); ASSERT_EQ(10.0, obj[Document::metaFieldTextScore].Double()); @@ -652,29 +717,29 @@ TEST(MetaFields, ToAndFromBson) { BSON_ARRAY("abc"_sd << "def"_sd)); Document fromBson = Document::fromBsonWithMetaData(obj); - ASSERT_TRUE(fromBson.hasTextScore()); - ASSERT_TRUE(fromBson.hasRandMetaField()); - ASSERT_EQ(10.0, fromBson.getTextScore()); - ASSERT_EQ(20, fromBson.getRandMetaField()); + ASSERT_TRUE(fromBson.metadata().hasTextScore()); + ASSERT_TRUE(fromBson.metadata().hasRandVal()); + ASSERT_EQ(10.0, fromBson.metadata().getTextScore()); + ASSERT_EQ(20, fromBson.metadata().getRandVal()); } TEST(MetaFields, MetaFieldsIncludedInDocumentApproximateSize) { MutableDocument docBuilder; - docBuilder.setSearchHighlights(DOC_ARRAY("abc"_sd - << "def"_sd)); + docBuilder.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd + << "def"_sd)); const size_t smallMetadataDocSize = docBuilder.freeze().getApproximateSize(); // The second document has a larger "search highlights" object. MutableDocument docBuilder2; - docBuilder2.setSearchHighlights(DOC_ARRAY("abc"_sd - << "def"_sd - << "ghijklmnop"_sd)); + docBuilder2.metadata().setSearchHighlights(DOC_ARRAY("abc"_sd + << "def"_sd + << "ghijklmnop"_sd)); Document doc2 = docBuilder2.freeze(); const size_t bigMetadataDocSize = doc2.getApproximateSize(); ASSERT_GT(bigMetadataDocSize, smallMetadataDocSize); // Do a sanity check on the amount of space taken by metadata in document 2. - ASSERT_LT(doc2.getMetadataApproximateSize(), 200U); + ASSERT_LT(doc2.getMetadataApproximateSize(), 250U); Document emptyDoc; ASSERT_LT(emptyDoc.getMetadataApproximateSize(), 100U); @@ -686,7 +751,7 @@ TEST(MetaFields, BadSerialization) { // Signal there are 0 fields. bb.appendNum(0); // This would specify a meta field with an invalid type. - bb.appendNum(char(MetaType::NUM_FIELDS) + 1); + bb.appendNum(char(DocumentMetadataFields::MetaType::NUM_FIELDS) + 1); // Signals end of input. bb.appendNum(char(0)); BufReader reader(bb.buf(), bb.len()); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index d28e2505030..0c0ef86f963 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -2585,15 +2585,16 @@ Value ExpressionMeta::serialize(bool explain) const { } Value ExpressionMeta::evaluate(const Document& root, Variables* variables) const { + const auto& metadata = root.metadata(); switch (_metaType) { case MetaType::TEXT_SCORE: - return root.hasTextScore() ? Value(root.getTextScore()) : Value(); + return metadata.hasTextScore() ? Value(metadata.getTextScore()) : Value(); case MetaType::RAND_VAL: - return root.hasRandMetaField() ? Value(root.getRandMetaField()) : Value(); + return metadata.hasRandVal() ? Value(metadata.getRandVal()) : Value(); case MetaType::SEARCH_SCORE: - return root.hasSearchScore() ? Value(root.getSearchScore()) : Value(); + return metadata.hasSearchScore() ? Value(metadata.getSearchScore()) : Value(); case MetaType::SEARCH_HIGHLIGHTS: - return root.hasSearchHighlights() ? Value(root.getSearchHighlights()) : Value(); + return metadata.hasSearchHighlights() ? Value(metadata.getSearchHighlights()) : Value(); } MONGO_UNREACHABLE; } diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index 403a4cf5882..34cd1a61335 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -6091,7 +6091,7 @@ TEST(ExpressionMetaTest, ExpressionMetaSearchScore) { auto expressionMeta = ExpressionMeta::parse(expCtx, expr.firstElement(), vps); MutableDocument doc; - doc.setSearchScore(1.234); + doc.metadata().setSearchScore(1.234); Value val = expressionMeta->evaluate(doc.freeze(), &expCtx->variables); ASSERT_EQ(val.getDouble(), 1.234); } @@ -6104,7 +6104,7 @@ TEST(ExpressionMetaTest, ExpressionMetaSearchHighlights) { MutableDocument doc; Document highlights = DOC("this part" << 1 << "is opaque to the server" << 1); - doc.setSearchHighlights(Value(highlights)); + doc.metadata().setSearchHighlights(Value(highlights)); Value val = expressionMeta->evaluate(doc.freeze(), &expCtx->variables); ASSERT_DOCUMENT_EQ(val.getDocument(), highlights); diff --git a/src/mongo/db/pipeline/parsed_add_fields_test.cpp b/src/mongo/db/pipeline/parsed_add_fields_test.cpp index 9d52327b985..e720908b080 100644 --- a/src/mongo/db/pipeline/parsed_add_fields_test.cpp +++ b/src/mongo/db/pipeline/parsed_add_fields_test.cpp @@ -545,8 +545,8 @@ TEST(ParsedAddFieldsExecutionTest, AlwaysKeepsMetadataFromOriginalDoc) { addition.parse(BSON("a" << true)); MutableDocument inputDocBuilder(Document{{"a", 1}}); - inputDocBuilder.setRandMetaField(1.0); - inputDocBuilder.setTextScore(10.0); + inputDocBuilder.metadata().setRandVal(1.0); + inputDocBuilder.metadata().setTextScore(10.0); Document inputDoc = inputDocBuilder.freeze(); auto result = addition.applyProjection(inputDoc); diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp index e0283ed101b..62a2875f18c 100644 --- a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp +++ b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp @@ -339,8 +339,8 @@ TEST(ExclusionProjectionExecutionTest, ShouldAlwaysKeepMetadataFromOriginalDoc) exclusion.parse(BSON("a" << false)); MutableDocument inputDocBuilder(Document{{"_id", "ID"_sd}, {"a", 1}}); - inputDocBuilder.setRandMetaField(1.0); - inputDocBuilder.setTextScore(10.0); + inputDocBuilder.metadata().setRandVal(1.0); + inputDocBuilder.metadata().setTextScore(10.0); Document inputDoc = inputDocBuilder.freeze(); auto result = exclusion.applyProjection(inputDoc); diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp index ca85af5fae1..09d4b0cb4d6 100644 --- a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp +++ b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp @@ -640,8 +640,8 @@ TEST(InclusionProjectionExecutionTest, ShouldAlwaysKeepMetadataFromOriginalDoc) inclusion.parse(BSON("a" << true)); MutableDocument inputDocBuilder(Document{{"a", 1}}); - inputDocBuilder.setRandMetaField(1.0); - inputDocBuilder.setTextScore(10.0); + inputDocBuilder.metadata().setRandVal(1.0); + inputDocBuilder.metadata().setTextScore(10.0); Document inputDoc = inputDocBuilder.freeze(); auto result = inclusion.applyProjection(inputDoc); diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index d64f43b4611..7720824f7f7 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -341,7 +341,7 @@ std::unique_ptr<ProjectionNode> analyzeProjection(const CanonicalQuery& query, const QueryRequest& qr = query.getQueryRequest(); // If there's no sort stage but we have a sortKey meta-projection, we need to add a stage to - // generate the sort key computed data. + // generate the sort key metadata. auto addSortKeyGeneratorStageIfNeeded = [&]() { if (!hasSortStage && query.getProj()->wantSortKey()) { auto keyGenNode = std::make_unique<SortKeyGeneratorNode>(); diff --git a/src/mongo/db/storage/index_entry_comparison.h b/src/mongo/db/storage/index_entry_comparison.h index 649333a36ab..41ff05f3dd3 100644 --- a/src/mongo/db/storage/index_entry_comparison.h +++ b/src/mongo/db/storage/index_entry_comparison.h @@ -45,6 +45,25 @@ namespace mongo { * and a disk location. */ struct IndexKeyEntry { + /** + * Given an index key 'dehyratedKey' with no field names, returns a new BSONObj representing the + * index key after adding field names according to 'keyPattern'. + */ + static BSONObj rehydrateKey(const BSONObj& keyPattern, const BSONObj& dehydratedKey) { + BSONObjBuilder bob; + BSONObjIterator keyIter(keyPattern); + BSONObjIterator valueIter(dehydratedKey); + + while (keyIter.more() && valueIter.more()) { + bob.appendAs(valueIter.next(), keyIter.next().fieldNameStringData()); + } + + invariant(!keyIter.more()); + invariant(!valueIter.more()); + + return bob.obj(); + } + IndexKeyEntry(BSONObj key, RecordId loc) : key(std::move(key)), loc(std::move(loc)) {} BSONObj key; diff --git a/src/mongo/dbtests/query_stage_sort_key_generator.cpp b/src/mongo/dbtests/query_stage_sort_key_generator.cpp index adc2bb2db50..daa667313e4 100644 --- a/src/mongo/dbtests/query_stage_sort_key_generator.cpp +++ b/src/mongo/dbtests/query_stage_sort_key_generator.cpp @@ -33,7 +33,6 @@ #include "mongo/db/exec/queued_data_stage.h" #include "mongo/db/exec/sort_key_generator.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/json.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/query_test_service_context.h" @@ -52,9 +51,7 @@ BSONObj extractKeyFromKeyGenStage(SortKeyGeneratorStage* sortKeyGen, WorkingSet* ASSERT_EQ(state, PlanStage::ADVANCED); auto wsm = workingSet->get(wsid); - auto sortKeyComputedData = - static_cast<const SortKeyComputedData*>(wsm->getComputed(WSM_SORT_KEY)); - return sortKeyComputedData->getSortKey(); + return wsm->metadata().getSortKey(); } /** diff --git a/src/mongo/s/query/router_stage_pipeline.cpp b/src/mongo/s/query/router_stage_pipeline.cpp index ce1c56c103b..b617af5ba01 100644 --- a/src/mongo/s/query/router_stage_pipeline.cpp +++ b/src/mongo/s/query/router_stage_pipeline.cpp @@ -97,7 +97,7 @@ BSONObj RouterStagePipeline::_validateAndConvertToBSON(const Document& event) { } // Confirm that the document _id field matches the original resume token in the sort key field. auto eventBSON = event.toBson(); - auto resumeToken = event.getSortKeyMetaField(); + auto resumeToken = event.metadata().getSortKey(); auto idField = eventBSON.getObjectField("_id"); invariant(!resumeToken.isEmpty()); uassert(ErrorCodes::ChangeStreamFatalError, |