diff options
author | Jacob Evans <jacob.evans@10gen.com> | 2018-11-20 15:51:46 -0500 |
---|---|---|
committer | Jacob Evans <jacob.evans@10gen.com> | 2018-12-28 17:40:50 -0500 |
commit | 53d9bc9d3ebc9dee0d374faeb23bb12bbf16a1b8 (patch) | |
tree | 6e9293215afa71412ccdf83504a770f4eb7390bc /src/mongo | |
parent | 6734c12d17dd4c0e2738a47feb7114221d6ba66d (diff) | |
download | mongo-53d9bc9d3ebc9dee0d374faeb23bb12bbf16a1b8.tar.gz |
SERVER-37831 Refactor ProjectionStage/ProjectionExec
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/exec/plan_stage.h | 11 | ||||
-rw-r--r-- | src/mongo/db/exec/projection.cpp | 319 | ||||
-rw-r--r-- | src/mongo/db/exec/projection.h | 152 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec.cpp | 291 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec.h | 122 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec_test.cpp | 397 | ||||
-rw-r--r-- | src/mongo/db/exec/working_set.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/exec/working_set.h | 24 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 29 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 35 |
10 files changed, 750 insertions, 651 deletions
diff --git a/src/mongo/db/exec/plan_stage.h b/src/mongo/db/exec/plan_stage.h index cd3f1963962..7aa5f725f85 100644 --- a/src/mongo/db/exec/plan_stage.h +++ b/src/mongo/db/exec/plan_stage.h @@ -109,6 +109,17 @@ public: PlanStage(const char* typeName, OperationContext* opCtx) : _commonStats(typeName), _opCtx(opCtx) {} +protected: + /** + * Obtain a PlanStage given a child stage. Called during the construction of derived + * PlanStage types with a single direct descendant. + */ + PlanStage(OperationContext* opCtx, std::unique_ptr<PlanStage> child, const char* typeName) + : PlanStage(typeName, opCtx) { + _children.push_back(std::move(child)); + } + +public: virtual ~PlanStage() {} using Children = std::vector<std::unique_ptr<PlanStage>>; diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp index ac6e3f660f2..18debacd536 100644 --- a/src/mongo/db/exec/projection.cpp +++ b/src/mongo/db/exec/projection.cpp @@ -32,9 +32,12 @@ #include "mongo/db/exec/projection.h" +#include "boost/optional.hpp" + #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" @@ -44,65 +47,82 @@ namespace mongo { -using std::endl; -using std::unique_ptr; -using std::vector; -using stdx::make_unique; - static const char* kIdField = "_id"; -// static -const char* ProjectionStage::kStageType = "PROJECTION"; +namespace { + +BSONObj indexKey(const WorkingSetMember& member) { + return static_cast<const IndexKeyComputedData*>(member.getComputed(WSM_INDEX_KEY))->getKey(); +} + +BSONObj sortKey(const WorkingSetMember& member) { + return static_cast<const SortKeyComputedData*>(member.getComputed(WSM_SORT_KEY))->getSortKey(); +} + +double geoDistance(const WorkingSetMember& member) { + return static_cast<const GeoDistanceComputedData*>( + member.getComputed(WSM_COMPUTED_GEO_DISTANCE)) + ->getDist(); +} + +BSONObj geoPoint(const WorkingSetMember& member) { + return static_cast<const GeoNearPointComputedData*>(member.getComputed(WSM_GEO_NEAR_POINT)) + ->getPoint(); +} + +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 + return 0.0; +} + +void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) { + member->keyData.clear(); + member->recordId = RecordId(); + member->obj = Snapshotted<BSONObj>(SnapshotId(), bo); + member->transitionToOwnedObj(); +} + +StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, + const WorkingSetMember& member) { + if (exec.needsGeoNearDistance() && !member.hasComputed(WSM_COMPUTED_GEO_DISTANCE)) + return Status(ErrorCodes::InternalError, "near loc dist requested but no data available"); + + if (exec.needsGeoNearPoint() && !member.hasComputed(WSM_GEO_NEAR_POINT)) + return Status(ErrorCodes::InternalError, "near loc proj requested but no data available"); + + return member.hasObj() + ? exec.project(member.obj.value(), + exec.needsGeoNearDistance() + ? boost::optional<const double>(geoDistance(member)) + : boost::none, + exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(), + exec.needsSortKey() ? sortKey(member) : BSONObj(), + exec.needsTextScore() ? boost::optional<const double>(textScore(member)) + : boost::none, + member.recordId.repr()) + : exec.projectCovered( + member.keyData, + exec.needsGeoNearDistance() ? boost::optional<const double>(geoDistance(member)) + : boost::none, + exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(), + exec.needsSortKey() ? sortKey(member) : BSONObj(), + exec.needsTextScore() ? boost::optional<const double>(textScore(member)) + : boost::none, + member.recordId.repr()); +} +} // namespace ProjectionStage::ProjectionStage(OperationContext* opCtx, - const ProjectionStageParams& params, + const BSONObj& projObj, WorkingSet* ws, - PlanStage* child) - : PlanStage(kStageType, opCtx), _ws(ws), _projImpl(params.projImpl) { - _children.emplace_back(child); - _projObj = params.projObj; - - if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { - _exec.reset( - new ProjectionExec(opCtx, params.projObj, params.fullExpression, params.collator)); - } else { - // We shouldn't need the full expression if we're fast-pathing. - invariant(NULL == params.fullExpression); - - // Sanity-check the input. - invariant(_projObj.isOwned()); - invariant(!_projObj.isEmpty()); - - // Figure out what fields are in the projection. - getSimpleInclusionFields(_projObj, &_includedFields); - - // If we're pulling data out of one index we can pre-compute the indices of the fields - // in the key that we pull data from and avoid looking up the field name each time. - if (ProjectionStageParams::COVERED_ONE_INDEX == params.projImpl) { - // Sanity-check. - _coveredKeyObj = params.coveredKeyObj; - invariant(_coveredKeyObj.isOwned()); - - BSONObjIterator kpIt(_coveredKeyObj); - while (kpIt.more()) { - BSONElement elt = kpIt.next(); - auto fieldIt = _includedFields.find(elt.fieldNameStringData()); - if (_includedFields.end() == fieldIt) { - // Push an unused value on the back to keep _includeKey and _keyFieldNames - // in sync. - _keyFieldNames.push_back(StringData()); - _includeKey.push_back(false); - } else { - // If we are including this key field store its field name. - _keyFieldNames.push_back(fieldIt->first); - _includeKey.push_back(true); - } - } - } else { - invariant(ProjectionStageParams::SIMPLE_DOC == params.projImpl); - } - } -} + std::unique_ptr<PlanStage> child) + : PlanStage(opCtx, std::move(child), kStageType), _projObj(projObj), _ws(*ws) {} // static void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields) { @@ -128,63 +148,6 @@ void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* } } -// static -void ProjectionStage::transformSimpleInclusion(const BSONObj& in, - const FieldSet& includedFields, - BSONObjBuilder& bob) { - // Look at every field in the source document and see if we're including it. - BSONObjIterator inputIt(in); - while (inputIt.more()) { - BSONElement elt = inputIt.next(); - auto fieldIt = includedFields.find(elt.fieldNameStringData()); - if (includedFields.end() != fieldIt) { - // If so, add it to the builder. - bob.append(elt); - } - } -} - -Status ProjectionStage::transform(WorkingSetMember* member) { - // The default no-fast-path case. - if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { - return _exec->transform(member); - } - - BSONObjBuilder bob; - - // SIMPLE_DOC implies that we expect an object so it's kind of redundant. - if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) { - // If we got here because of SIMPLE_DOC the planner shouldn't have messed up. - invariant(member->hasObj()); - - // Apply the SIMPLE_DOC projection. - transformSimpleInclusion(member->obj.value(), _includedFields, bob); - } else { - invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl); - // We're pulling data out of the key. - invariant(1 == member->keyData.size()); - size_t keyIndex = 0; - - // Look at every key element... - BSONObjIterator keyIterator(member->keyData[0].keyData); - while (keyIterator.more()) { - BSONElement elt = keyIterator.next(); - // If we're supposed to include it... - if (_includeKey[keyIndex]) { - // Do so. - bob.appendAs(elt, _keyFieldNames[keyIndex]); - } - ++keyIndex; - } - } - - member->keyData.clear(); - member->recordId = RecordId(); - member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj()); - member->transitionToOwnedObj(); - return Status::OK(); -} - bool ProjectionStage::isEOF() { return child()->isEOF(); } @@ -196,12 +159,12 @@ PlanStage::StageState ProjectionStage::doWork(WorkingSetID* out) { // Note that we don't do the normal if isEOF() return EOF thing here. Our child might be a // tailable cursor and isEOF() would be true even if it had more data... if (PlanStage::ADVANCED == status) { - WorkingSetMember* member = _ws->get(id); + WorkingSetMember* member = _ws.get(id); // Punt to our specific projection impl. Status projStatus = transform(member); if (!projStatus.isOK()) { warning() << "Couldn't execute projection, status = " << redact(projStatus); - *out = WorkingSetCommon::allocateStatusMember(_ws, projStatus); + *out = WorkingSetCommon::allocateStatusMember(&_ws, projStatus); return PlanStage::FAILURE; } @@ -218,11 +181,11 @@ PlanStage::StageState ProjectionStage::doWork(WorkingSetID* out) { return status; } -unique_ptr<PlanStageStats> ProjectionStage::getStats() { +std::unique_ptr<PlanStageStats> ProjectionStage::getStats() { _commonStats.isEOF = isEOF(); - unique_ptr<PlanStageStats> ret = make_unique<PlanStageStats>(_commonStats, STAGE_PROJECTION); + auto ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_PROJECTION); - unique_ptr<ProjectionStats> projStats = make_unique<ProjectionStats>(_specificStats); + auto projStats = std::make_unique<ProjectionStats>(_specificStats); projStats->projObj = _projObj; ret->specific = std::move(projStats); @@ -230,8 +193,128 @@ unique_ptr<PlanStageStats> ProjectionStage::getStats() { return ret; } -const SpecificStats* ProjectionStage::getSpecificStats() const { - return &_specificStats; +ProjectionStageDefault::ProjectionStageDefault(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child, + const MatchExpression* fullExpression, + const CollatorInterface* collator) + : ProjectionStage(opCtx, projObj, ws, std::move(child)), + _exec(opCtx, projObj, fullExpression, collator) {} + +Status ProjectionStageDefault::transform(WorkingSetMember* member) const { + // The default no-fast-path case. + if (_exec.needsSortKey() && !member->hasComputed(WSM_SORT_KEY)) + 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(), + _exec.needsSortKey() ? sortKey(*member) : BSONObj()); + if (!keys.isOK()) + return keys.getStatus(); + + transitionMemberToOwnedObj(keys.getValue(), member); + return Status::OK(); + } + + auto projected = provideMetaFieldsAndPerformExec(_exec, *member); + + if (!projected.isOK()) + return projected.getStatus(); + + transitionMemberToOwnedObj(projected.getValue(), member); + + return Status::OK(); +} + +ProjectionStageCovered::ProjectionStageCovered(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child, + const BSONObj& coveredKeyObj) + : ProjectionStage(opCtx, projObj, ws, std::move(child)), _coveredKeyObj(coveredKeyObj) { + invariant(projObjHasOwnedData()); + // Figure out what fields are in the projection. + getSimpleInclusionFields(_projObj, &_includedFields); + + // If we're pulling data out of one index we can pre-compute the indices of the fields + // in the key that we pull data from and avoid looking up the field name each time. + + // Sanity-check. + invariant(_coveredKeyObj.isOwned()); + + BSONObjIterator kpIt(_coveredKeyObj); + while (kpIt.more()) { + BSONElement elt = kpIt.next(); + auto fieldIt = _includedFields.find(elt.fieldNameStringData()); + if (_includedFields.end() == fieldIt) { + // Push an unused value on the back to keep _includeKey and _keyFieldNames + // in sync. + _keyFieldNames.push_back(StringData()); + _includeKey.push_back(false); + } else { + // If we are including this key field store its field name. + _keyFieldNames.push_back(fieldIt->first); + _includeKey.push_back(true); + } + } +} + +Status ProjectionStageCovered::transform(WorkingSetMember* member) const { + BSONObjBuilder bob; + + // We're pulling data out of the key. + invariant(1 == member->keyData.size()); + size_t keyIndex = 0; + + // Look at every key element... + BSONObjIterator keyIterator(member->keyData[0].keyData); + while (keyIterator.more()) { + BSONElement elt = keyIterator.next(); + // If we're supposed to include it... + if (_includeKey[keyIndex]) { + // Do so. + bob.appendAs(elt, _keyFieldNames[keyIndex]); + } + ++keyIndex; + } + + transitionMemberToOwnedObj(bob.obj(), member); + return Status::OK(); +} + +ProjectionStageSimple::ProjectionStageSimple(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child) + : ProjectionStage(opCtx, projObj, ws, std::move(child)) { + invariant(projObjHasOwnedData()); + // Figure out what fields are in the projection. + getSimpleInclusionFields(_projObj, &_includedFields); +} + +Status ProjectionStageSimple::transform(WorkingSetMember* member) const { + BSONObjBuilder bob; + // SIMPLE_DOC implies that we expect an object so it's kind of redundant. + // If we got here because of SIMPLE_DOC the planner shouldn't have messed up. + invariant(member->hasObj()); + + // Apply the SIMPLE_DOC projection. + // Look at every field in the source document and see if we're including it. + BSONObjIterator inputIt(member->obj.value()); + while (inputIt.more()) { + BSONElement elt = inputIt.next(); + auto fieldIt = _includedFields.find(elt.fieldNameStringData()); + if (_includedFields.end() != fieldIt) { + // If so, add it to the builder. + bob.append(elt); + } + } + + transitionMemberToOwnedObj(bob.obj(), member); + return Status::OK(); } } // namespace mongo diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h index e8caf8b5db6..b28cdcd9302 100644 --- a/src/mongo/db/exec/projection.h +++ b/src/mongo/db/exec/projection.h @@ -41,46 +41,19 @@ namespace mongo { class CollatorInterface; -struct ProjectionStageParams { - enum ProjectionImplementation { - // The default case. Will handle every projection. - NO_FAST_PATH, - - // The projection is simple inclusion and is totally covered by one index. - COVERED_ONE_INDEX, - - // The projection is simple inclusion and we expect an object. - SIMPLE_DOC - }; - - ProjectionImplementation projImpl = NO_FAST_PATH; - - // The projection object. We lack a ProjectionExpression or similar so we use a BSONObj. - BSONObj projObj; - - // If we have a positional or elemMatch projection we need a MatchExpression to pull out the - // right data. - // Not owned here, we do not take ownership. - const MatchExpression* fullExpression = nullptr; - - // If (COVERED_ONE_INDEX == projObj) this is the key pattern we're extracting covered data - // from. Otherwise, this field is ignored. - BSONObj coveredKeyObj; - - // The collator this operation should use to compare strings. If null, the collation is a simple - // binary compare. - const CollatorInterface* collator = nullptr; -}; - /** - * This stage computes a projection. + * This stage computes a projection. This is an abstract base class for various projection + * implementations. */ -class ProjectionStage final : public PlanStage { -public: +class ProjectionStage : public PlanStage { +protected: ProjectionStage(OperationContext* opCtx, - const ProjectionStageParams& params, + const BSONObj& projObj, WorkingSet* ws, - PlanStage* child); + std::unique_ptr<PlanStage> child); + +public: + static constexpr const char* kStageType = "PROJECTION"; bool isEOF() final; StageState doWork(WorkingSetID* out) final; @@ -89,10 +62,13 @@ public: return STAGE_PROJECTION; } - std::unique_ptr<PlanStageStats> getStats(); + std::unique_ptr<PlanStageStats> getStats() final; - const SpecificStats* getSpecificStats() const final; + const SpecificStats* getSpecificStats() const final { + return &_specificStats; + } +protected: using FieldSet = StringMap<bool>; // Value is unused. /** @@ -102,42 +78,74 @@ public: */ static void getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields); + bool projObjHasOwnedData() { + return _projObj.isOwned() && !_projObj.isEmpty(); + } + + // The projection object used by all projection implementations. We lack a ProjectionExpression + // or similar so we use a BSONObj. + BSONObj _projObj; + +private: /** - * Applies a simple inclusion projection to 'in', including - * only the fields specified by 'includedFields'. - * - * The resulting document is constructed using 'bob'. + * Runs either the default complete implementation or a fast path depending on how this was + * constructed. */ - static void transformSimpleInclusion(const BSONObj& in, - const FieldSet& includedFields, - BSONObjBuilder& bob); + virtual Status transform(WorkingSetMember* member) const = 0; - static const char* kStageType; + // Used to retrieve a WorkingSetMember as part of 'doWork()'. + WorkingSet& _ws; -private: - Status transform(WorkingSetMember* member); + // Populated by 'getStats()'. + ProjectionStats _specificStats; +}; - std::unique_ptr<ProjectionExec> _exec; +/** + * The default case. Can handle every projection. + */ +class ProjectionStageDefault final : public ProjectionStage { +public: + /** + * ProjectionNode::DEFAULT should use this for construction. + */ + ProjectionStageDefault(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child, + const MatchExpression* fullExpression, + const CollatorInterface* collator); - // _ws is not owned by us. - WorkingSet* _ws; +private: + Status transform(WorkingSetMember* member) const final; - // Stats - ProjectionStats _specificStats; + // Fully-general heavy execution object. + ProjectionExec _exec; +}; - // Fast paths: - ProjectionStageParams::ProjectionImplementation _projImpl; +/** + * This class is used when the projection is totally covered by one index and the following rules + * are met: the projection consists only of inclusions e.g. '{field: 1}', it has no $meta + * projections, it is not a returnKey projection and it has no dotted fields. + */ +class ProjectionStageCovered final : public ProjectionStage { +public: + /** + * ProjectionNode::COVERED_ONE_INDEX should obtain a fast-path object through this constructor. + */ + ProjectionStageCovered(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child, + const BSONObj& coveredKeyObj); - // Used by all projection implementations. - BSONObj _projObj; +private: + Status transform(WorkingSetMember* member) const final; - // Data used for both SIMPLE_DOC and COVERED_ONE_INDEX paths. // Has the field names present in the simple projection. FieldSet _includedFields; - // - // Used for the COVERED_ONE_INDEX path. - // + // This is the key pattern we're extracting covered data from. It is maintained here since + // strings derived from it depend on its lifetime. BSONObj _coveredKeyObj; // Field names can be empty in 2.4 and before so we can't use them as a sentinel value. @@ -148,4 +156,26 @@ private: std::vector<StringData> _keyFieldNames; }; +/** + * This class is used when we expect an object and the following rules are met: the projection + * consists only of inclusions e.g. '{field: 1}', it has no $meta projections, it is not a returnKey + * projection and it has no dotted fields. + */ +class ProjectionStageSimple final : public ProjectionStage { +public: + /** + * ProjectionNode::SIMPLE_DOC should obtain a fast-path object through this constructor. + */ + ProjectionStageSimple(OperationContext* opCtx, + const BSONObj& projObj, + WorkingSet* ws, + std::unique_ptr<PlanStage> child); + +private: + Status transform(WorkingSetMember* member) const final; + + // Has the field names present in the simple projection. + FieldSet _includedFields; +}; + } // namespace mongo diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp index 83e5ad721e9..e1240539f38 100644 --- a/src/mongo/db/exec/projection_exec.cpp +++ b/src/mongo/db/exec/projection_exec.cpp @@ -31,7 +31,6 @@ #include "mongo/db/exec/projection_exec.h" #include "mongo/bson/mutable/document.h" -#include "mongo/db/exec/working_set_computed_data.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/query/collation/collator_interface.h" @@ -41,58 +40,15 @@ namespace mongo { -using std::max; using std::string; namespace mmb = mongo::mutablebson; -namespace { - -/** - * Adds sort key metadata inside 'member' to 'builder' with field name 'fieldName'. - * - * Returns a non-OK status if sort key metadata is missing from 'member'. - */ -Status addSortKeyMetaProj(StringData fieldName, - const WorkingSetMember& member, - BSONObjBuilder* builder) { - if (!member.hasComputed(WSM_SORT_KEY)) { - return Status(ErrorCodes::InternalError, - "sortKey meta-projection requested but no data available"); - } - - const SortKeyComputedData* sortKeyData = - static_cast<const SortKeyComputedData*>(member.getComputed(WSM_SORT_KEY)); - builder->append(fieldName, sortKeyData->getSortKey()); - return Status::OK(); -} - -} // namespace - -ProjectionExec::ProjectionExec() - : _include(true), - _special(false), - _includeID(true), - _skip(0), - _limit(-1), - _arrayOpType(ARRAY_OP_NORMAL), - _queryExpression(NULL), - _hasReturnKey(false) {} - ProjectionExec::ProjectionExec(OperationContext* opCtx, const BSONObj& spec, const MatchExpression* queryExpression, const CollatorInterface* collator) - : _include(true), - _special(false), - _source(spec), - _includeID(true), - _skip(0), - _limit(-1), - _arrayOpType(ARRAY_OP_NORMAL), - _queryExpression(queryExpression), - _hasReturnKey(false), - _collator(collator) { + : _source(spec), _queryExpression(queryExpression), _collator(collator) { // Whether we're including or excluding fields. enum class IncludeExclude { kUninitialized, kInclude, kExclude }; IncludeExclude includeExclude = IncludeExclude::kUninitialized; @@ -103,7 +59,7 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx, if (Object == e.type()) { BSONObj obj = e.embeddedObject(); - verify(1 == obj.nFields()); + invariant(1 == obj.nFields()); BSONElement e2 = obj.firstElement(); if (mongoutils::str::equals(e2.fieldName(), "$slice")) { @@ -115,15 +71,15 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx, add(e.fieldName(), 0, i); } } else { - verify(e2.type() == Array); + invariant(e2.type() == Array); BSONObj arr = e2.embeddedObject(); - verify(2 == arr.nFields()); + invariant(2 == arr.nFields()); BSONObjIterator it(arr); int skip = it.next().numberInt(); int limit = it.next().numberInt(); - verify(limit > 0); + invariant(limit > 0); add(e.fieldName(), skip, limit); } @@ -132,39 +88,43 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx, // Create a MatchExpression for the elemMatch. BSONObj elemMatchObj = e.wrap(); - verify(elemMatchObj.isOwned()); + invariant(elemMatchObj.isOwned()); _elemMatchObjs.push_back(elemMatchObj); boost::intrusive_ptr<ExpressionContext> expCtx( new ExpressionContext(opCtx, _collator)); StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(elemMatchObj, std::move(expCtx)); - verify(statusWithMatcher.isOK()); + invariant(statusWithMatcher.isOK()); // And store it in _matchers. _matchers[mongoutils::str::before(e.fieldName(), '.').c_str()] = statusWithMatcher.getValue().release(); add(e.fieldName(), true); } else if (mongoutils::str::equals(e2.fieldName(), "$meta")) { - verify(String == e2.type()); + invariant(String == e2.type()); if (e2.valuestr() == QueryRequest::metaTextScore) { _meta[e.fieldName()] = META_TEXT_SCORE; + _needsTextScore = true; } else if (e2.valuestr() == QueryRequest::metaSortKey) { _sortKeyMetaFields.push_back(e.fieldName()); _meta[_sortKeyMetaFields.back()] = META_SORT_KEY; + _needsSortKey = true; } else if (e2.valuestr() == QueryRequest::metaRecordId) { _meta[e.fieldName()] = META_RECORDID; } else if (e2.valuestr() == QueryRequest::metaGeoNearPoint) { _meta[e.fieldName()] = META_GEONEAR_POINT; + _needsGeoNearPoint = true; } else if (e2.valuestr() == QueryRequest::metaGeoNearDistance) { _meta[e.fieldName()] = META_GEONEAR_DIST; + _needsGeoNearDistance = true; } else if (e2.valuestr() == QueryRequest::metaIndexKey) { _hasReturnKey = true; } else { // This shouldn't happen, should be caught by parsing. - verify(0); + MONGO_UNREACHABLE; } } else { - verify(0); + MONGO_UNREACHABLE; } } else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; @@ -240,147 +200,140 @@ void ProjectionExec::add(const string& field, int skip, int limit) { // Execution // -Status ProjectionExec::transform(WorkingSetMember* member) const { - if (_hasReturnKey) { - BSONObjBuilder builder; +StatusWith<BSONObj> ProjectionExec::computeReturnKeyProjection(const BSONObj& indexKey, + const BSONObj& sortKey) const { + BSONObjBuilder bob; - if (member->hasComputed(WSM_INDEX_KEY)) { - const IndexKeyComputedData* key = - static_cast<const IndexKeyComputedData*>(member->getComputed(WSM_INDEX_KEY)); - builder.appendElements(key->getKey()); - } + if (!indexKey.isEmpty()) { + bob.appendElements(indexKey); + } - // Must be possible to do both returnKey meta-projection and sortKey meta-projection so that - // mongos can support returnKey. - for (auto fieldName : _sortKeyMetaFields) { - auto sortKeyMetaStatus = addSortKeyMetaProj(fieldName, *member, &builder); - if (!sortKeyMetaStatus.isOK()) { - return sortKeyMetaStatus; - } - } + // Must be possible to do both returnKey meta-projection and sortKey meta-projection so that + // mongos can support returnKey. + for (auto fieldName : _sortKeyMetaFields) + bob.append(fieldName, sortKey); - member->obj = Snapshotted<BSONObj>(SnapshotId(), builder.obj()); - member->keyData.clear(); - member->recordId = RecordId(); - member->transitionToOwnedObj(); - return Status::OK(); - } + return bob.obj(); +} +StatusWith<BSONObj> ProjectionExec::project(const BSONObj& in, + const boost::optional<const double> geoDistance, + const BSONObj& geoNearPoint, + const BSONObj& sortKey, + const boost::optional<const double> textScore, + const int64_t recordId) const { BSONObjBuilder bob; - if (member->hasObj()) { - MatchDetails matchDetails; - - // If it's a positional projection we need a MatchDetails. - if (transformRequiresDetails()) { - matchDetails.requestElemMatchKey(); - verify(NULL != _queryExpression); - verify(_queryExpression->matchesBSON(member->obj.value(), &matchDetails)); - } + MatchDetails matchDetails; - Status projStatus = transform(member->obj.value(), &bob, &matchDetails); - if (!projStatus.isOK()) { - return projStatus; - } - } else { - invariant(!_include); - // Go field by field. - if (_includeID) { - BSONElement elt; - // Sometimes the _id field doesn't exist... - if (member->getFieldDotted("_id", &elt) && !elt.eoo()) { - bob.appendAs(elt, "_id"); - } + // If it's a positional projection we need a MatchDetails. + if (transformRequiresDetails()) { + matchDetails.requestElemMatchKey(); + invariant(nullptr != _queryExpression); + invariant(_queryExpression->matchesBSON(in, &matchDetails)); + } + + Status projStatus = projectHelper(in, &bob, &matchDetails); + if (!projStatus.isOK()) + return projStatus; + else + return {addMeta(std::move(bob), geoDistance, geoNearPoint, sortKey, textScore, recordId)}; +} + +StatusWith<BSONObj> ProjectionExec::projectCovered(const std::vector<IndexKeyDatum>& keyData, + const boost::optional<const double> geoDistance, + const BSONObj& geoNearPoint, + const BSONObj& sortKey, + const boost::optional<const double> textScore, + const int64_t recordId) const { + invariant(!_include); + BSONObjBuilder bob; + // Go field by field. + if (_includeID) { + boost::optional<BSONElement> elt; + // Sometimes the _id field doesn't exist... + if ((elt = IndexKeyDatum::getFieldDotted(keyData, "_id")) && !elt->eoo()) { + bob.appendAs(elt.get(), "_id"); } + } - mmb::Document projectedDoc; + mmb::Document projectedDoc; - for (auto&& specElt : _source) { - if (mongoutils::str::equals("_id", specElt.fieldName())) { - continue; - } + for (auto&& specElt : _source) { + if (mongoutils::str::equals("_id", specElt.fieldName())) { + continue; + } - // $meta sortKey is the only meta-projection which is allowed to operate on index keys - // rather than the full document. - auto metaIt = _meta.find(specElt.fieldName()); - if (metaIt != _meta.end()) { - invariant(metaIt->second == META_SORT_KEY); - continue; - } + // $meta sortKey is the only meta-projection which is allowed to operate on index keys + // rather than the full document. + auto metaIt = _meta.find(specElt.fieldName()); + if (metaIt != _meta.end()) { + invariant(metaIt->second == META_SORT_KEY); + continue; + } - // $meta sortKey is also the only element with an Object value in the projection spec - // that can operate on index keys rather than the full document. - invariant(BSONType::Object != specElt.type()); - - BSONElement keyElt; - // We can project a field that doesn't exist. We just ignore it. - if (member->getFieldDotted(specElt.fieldName(), &keyElt) && !keyElt.eoo()) { - FieldRef projectedFieldPath{specElt.fieldNameStringData()}; - auto setElementStatus = - pathsupport::setElementAtPath(projectedFieldPath, keyElt, &projectedDoc); - if (!setElementStatus.isOK()) { - return setElementStatus; - } + // $meta sortKey is also the only element with an Object value in the projection spec + // that can operate on index keys rather than the full document. + invariant(BSONType::Object != specElt.type()); + + boost::optional<BSONElement> keyElt; + // We can project a field that doesn't exist. We just ignore it. + if ((keyElt = IndexKeyDatum::getFieldDotted(keyData, specElt.fieldName())) && + !keyElt->eoo()) { + FieldRef projectedFieldPath{specElt.fieldNameStringData()}; + auto setElementStatus = + pathsupport::setElementAtPath(projectedFieldPath, keyElt.get(), &projectedDoc); + if (!setElementStatus.isOK()) { + return setElementStatus; } } - - bob.appendElements(projectedDoc.getObject()); } + bob.appendElements(projectedDoc.getObject()); + return {addMeta(std::move(bob), geoDistance, geoNearPoint, sortKey, textScore, recordId)}; +} + +BSONObj ProjectionExec::addMeta(BSONObjBuilder bob, + const boost::optional<const double> geoDistance, + const BSONObj& geoNearPoint, + const BSONObj& sortKey, + const boost::optional<const double> textScore, + const int64_t recordId) const { for (MetaMap::const_iterator it = _meta.begin(); it != _meta.end(); ++it) { - if (META_GEONEAR_DIST == it->second) { - if (member->hasComputed(WSM_COMPUTED_GEO_DISTANCE)) { - const GeoDistanceComputedData* dist = static_cast<const GeoDistanceComputedData*>( - member->getComputed(WSM_COMPUTED_GEO_DISTANCE)); - bob.append(it->first, dist->getDist()); - } else { - return Status(ErrorCodes::InternalError, - "near loc dist requested but no data available"); - } - } else if (META_GEONEAR_POINT == it->second) { - if (member->hasComputed(WSM_GEO_NEAR_POINT)) { - const GeoNearPointComputedData* point = - static_cast<const GeoNearPointComputedData*>( - member->getComputed(WSM_GEO_NEAR_POINT)); - BSONObj ptObj = point->getPoint(); + switch (it->second) { + case META_GEONEAR_DIST: + invariant(geoDistance); + 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); } - } else { - return Status(ErrorCodes::InternalError, - "near loc proj requested but no data available"); - } - } else if (META_TEXT_SCORE == it->second) { - if (member->hasComputed(WSM_COMPUTED_TEXT_SCORE)) { - const TextScoreComputedData* score = static_cast<const TextScoreComputedData*>( - member->getComputed(WSM_COMPUTED_TEXT_SCORE)); - bob.append(it->first, score->getScore()); - } else { - bob.append(it->first, 0.0); + break; } - } else if (META_SORT_KEY == it->second) { - auto sortKeyMetaStatus = addSortKeyMetaProj(it->first, *member, &bob); - if (!sortKeyMetaStatus.isOK()) { - return sortKeyMetaStatus; + case META_TEXT_SCORE: + invariant(textScore); + bob.append(it->first, textScore.get()); + break; + case META_SORT_KEY: { + invariant(!sortKey.isEmpty()); + bob.append(it->first, sortKey); + break; } - } else if (META_RECORDID == it->second) { - bob.append(it->first, static_cast<long long>(member->recordId.repr())); + case META_RECORDID: + invariant(recordId != 0); + bob.append(it->first, recordId); } } - - BSONObj newObj = bob.obj(); - member->obj = Snapshotted<BSONObj>(SnapshotId(), newObj); - member->keyData.clear(); - member->recordId = RecordId(); - member->transitionToOwnedObj(); - - return Status::OK(); + return bob.obj(); } -Status ProjectionExec::transform(const BSONObj& in, - BSONObjBuilder* bob, - const MatchDetails* details) const { +Status ProjectionExec::projectHelper(const BSONObj& in, + BSONObjBuilder* bob, + const MatchDetails* details) const { const ArrayOpType& arrayOpType = _arrayOpType; BSONObjIterator it(in); @@ -451,7 +404,7 @@ void ProjectionExec::appendArray(BSONObjBuilder* bob, const BSONObj& array, bool int limit = nested ? -1 : _limit; if (skip < 0) { - skip = max(0, skip + array.nFields()); + skip = std::max(0, skip + array.nFields()); } int index = 0; diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h index 93af3c083ed..74676d6d1fe 100644 --- a/src/mongo/db/exec/projection_exec.h +++ b/src/mongo/db/exec/projection_exec.h @@ -30,6 +30,8 @@ #pragma once +#include "boost/optional.hpp" + #include "mongo/db/exec/working_set.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" @@ -40,6 +42,9 @@ namespace mongo { class CollatorInterface; +/** + * A fully-featured executor for find projection. + */ class ProjectionExec { public: /** @@ -55,7 +60,6 @@ public: enum MetaProjection { META_GEONEAR_DIST, META_GEONEAR_POINT, - META_IX_KEY, META_RECORDID, META_SORT_KEY, META_TEXT_SCORE, @@ -76,16 +80,95 @@ public: ~ProjectionExec(); /** - * Apply this projection to the 'member'. Changes the type to OWNED_OBJ. + * Indicates whether this is a returnKey projection which should be performed via + * 'computeReturnKeyProjection()'. + */ + bool returnKey() const { + return _hasReturnKey; + } + + /** + * Indicates whether 'sortKey' must be provided for 'computeReturnKeyProjection()' or + * 'project()'. + */ + bool needsSortKey() const { + return _needsSortKey; + } + + /** + * Indicates whether 'geoDistance' must be provided for 'project()'. + */ + bool needsGeoNearDistance() const { + return _needsGeoNearDistance; + } + + /** + * Indicates whether 'geoNearPoint' must be provided for 'project()'. + */ + bool needsGeoNearPoint() const { + return _needsGeoNearPoint; + } + + /** + * Indicates whether 'textScore' is going to be used in 'project()'. + */ + bool needsTextScore() const { + return _needsTextScore; + } + + /** + * Returns false if there are no meta fields to project. + */ + bool hasMetaFields() const { + return !_meta.empty(); + } + + /** + * Performs a returnKey projection and provides index keys rather than projection results. */ - Status transform(WorkingSetMember* member) const; + StatusWith<BSONObj> computeReturnKeyProjection(const BSONObj& indexKey, + const BSONObj& sortKey) const; + + /** + * Performs a projection given a BSONObj source. Meta fields must be provided if necessary. + * Their necessity can be queried via the 'needs*' functions. + */ + StatusWith<BSONObj> project(const BSONObj& in, + const boost::optional<const double> geoDistance = boost::none, + const BSONObj& geoNearPoint = BSONObj(), + const BSONObj& sortKey = BSONObj(), + const boost::optional<const double> textScore = boost::none, + const int64_t recordId = 0) const; + + /** + * Performs a projection given a function to retrieve fields by name. This function handles + * projections which do not qualify for the COVERED_ONE_INDEX fast-path but are still coverd by + * indices. + */ + StatusWith<BSONObj> projectCovered( + const std::vector<IndexKeyDatum>& keyData, + const boost::optional<const double> geoDistance = boost::none, + const BSONObj& geoNearPoint = BSONObj(), + const BSONObj& sortKey = BSONObj(), + const boost::optional<const double> textScore = boost::none, + const int64_t recordId = 0) const; private: + /** + * Adds meta fields to the end of a projection. + */ + BSONObj addMeta(BSONObjBuilder bob, + const boost::optional<const double> geoDistance, + const BSONObj& geoNearPoint, + const BSONObj& sortKey, + const boost::optional<const double> textScore, + const int64_t recordId) const; + // // Initialization // - ProjectionExec(); + ProjectionExec() = default; /** * Add 'field' as a field name that is included or excluded as part of the projection. @@ -110,9 +193,9 @@ private: * 'bob'. * Otherwise, returns error. */ - Status transform(const BSONObj& in, - BSONObjBuilder* bob, - const MatchDetails* details = NULL) const; + Status projectHelper(const BSONObj& in, + BSONObjBuilder* bob, + const MatchDetails* details = nullptr) const; /** * See transform(...) above. @@ -137,10 +220,10 @@ private: void appendArray(BSONObjBuilder* bob, const BSONObj& array, bool nested = false) const; // True if default at this level is to include. - bool _include; + bool _include = true; // True if this level can't be skipped or included without recursing. - bool _special; + bool _special = false; // We must group projections with common prefixes together. // TODO: benchmark std::vector<pair> vs map @@ -154,11 +237,11 @@ private: BSONObj _source; // Should we include the _id field? - bool _includeID; + bool _includeID = true; // Arguments from the $slice operator. - int _skip; - int _limit; + int _skip = 0; + int _limit = -1; // Used for $elemMatch and positional operator ($) Matchers _matchers; @@ -166,17 +249,24 @@ private: // The matchers above point into BSONObjs and this is where those objs live. std::vector<BSONObj> _elemMatchObjs; - ArrayOpType _arrayOpType; + ArrayOpType _arrayOpType = ARRAY_OP_NORMAL; - // The full query expression. Used when we need MatchDetails. - const MatchExpression* _queryExpression; + // The full query expression. Used when we need MatchDetails. + const MatchExpression* _queryExpression = nullptr; // Projections that aren't sourced from the document or index keys. MetaMap _meta; // Do we have a returnKey projection? If so we *only* output the index key metadata, and // possibly the sort key for mongos to use. If it's not found we output nothing. - bool _hasReturnKey; + bool _hasReturnKey = false; + + // After parsing in the constructor, these fields will indicate the neccesity of metadata + // for $meta projection. + bool _needsSortKey = false; + bool _needsGeoNearDistance = false; + bool _needsGeoNearPoint = false; + bool _needsTextScore = false; // The field names associated with any sortKey meta-projection(s). Empty if there is no sortKey // meta-projection. diff --git a/src/mongo/db/exec/projection_exec_test.cpp b/src/mongo/db/exec/projection_exec_test.cpp index 9c9e4f5f090..9a05cfabe05 100644 --- a/src/mongo/db/exec/projection_exec_test.cpp +++ b/src/mongo/db/exec/projection_exec_test.cpp @@ -32,6 +32,10 @@ * This file contains tests for mongo/db/exec/projection_exec.cpp */ +#include "boost/optional.hpp" +#include "boost/optional/optional_io.hpp" +#include <memory> + #include "mongo/db/exec/projection_exec.h" #include "mongo/db/exec/working_set_computed_data.h" @@ -39,159 +43,56 @@ #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/stdx/variant.h" #include "mongo/unittest/unittest.h" -#include <memory> using namespace mongo; +using namespace std::string_literals; namespace { -using std::unique_ptr; - /** - * Utility function to create MatchExpression + * Utility function to create a MatchExpression. */ -unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { +std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression status = MatchExpressionParser::parse(obj, std::move(expCtx)); ASSERT_TRUE(status.isOK()); return std::move(status.getValue()); } -// -// transform tests -// - /** - * test function to verify results of transform() - * on a working set member. - * - * specStr - projection specification - * queryStr - query - * objStr - object to run projection on - * data - computed data. Owned by working set member created in this function if not null. - * expectedStatusOK - expected status of transformation - * expectedObjStr - expected object after successful projection. - * Ignored if expectedStatusOK is false. + * Test encapsulation for single call to ProjectionExec::project() or + * ProjectionExec::projectCovered(). */ - -void testTransform(const char* specStr, - const char* queryStr, - const char* objStr, - WorkingSetComputedData* data, - const CollatorInterface* collator, - bool expectedStatusOK, - const char* expectedObjStr) { +boost::optional<std::string> project( + const char* specStr, + const char* queryStr, + const stdx::variant<const char*, const IndexKeyDatum> objStrOrDatum, + const boost::optional<const CollatorInterface&> collator = boost::none, + const BSONObj& sortKey = BSONObj(), + const double textScore = 0.0) { // Create projection exec object. BSONObj spec = fromjson(specStr); BSONObj query = fromjson(queryStr); - unique_ptr<MatchExpression> queryExpression = parseMatchExpression(query); - QueryTestServiceContext serviceCtx; - auto opCtx = serviceCtx.makeOperationContext(); - ProjectionExec exec(opCtx.get(), spec, queryExpression.get(), collator); - - // Create working set member. - WorkingSetMember wsm; - wsm.obj = Snapshotted<BSONObj>(SnapshotId(), fromjson(objStr)); - if (data) { - wsm.addComputed(data); - } - wsm.transitionToOwnedObj(); - - // Transform object - Status status = exec.transform(&wsm); - - // There are fewer checks to perform if we are expected a failed status. - if (!expectedStatusOK) { - if (status.isOK()) { - mongoutils::str::stream ss; - ss << "expected transform() to fail but got success instead." - << "\nprojection spec: " << specStr << "\nquery: " << queryStr - << "\nobject before projection: " << objStr; - FAIL(ss); - } - return; - } - - // If we are expecting a successful transformation but got a failed status instead, - // print out status message in assertion message. - if (!status.isOK()) { - mongoutils::str::stream ss; - ss << "transform() test failed: unexpected failed status: " << status.toString() - << "\nprojection spec: " << specStr << "\nquery: " << queryStr - << "\nobject before projection: " << objStr - << "\nexpected object after projection: " << expectedObjStr; - FAIL(ss); - } - - // Finally, we compare the projected object. - const BSONObj& obj = wsm.obj.value(); - BSONObj expectedObj = fromjson(expectedObjStr); - if (SimpleBSONObjComparator::kInstance.evaluate(obj != expectedObj)) { - mongoutils::str::stream ss; - ss << "transform() test failed: unexpected projected object." - << "\nprojection spec: " << specStr << "\nquery: " << queryStr - << "\nobject before projection: " << objStr - << "\nexpected object after projection: " << expectedObjStr - << "\nactual object after projection: " << obj.toString(); - FAIL(ss); - } -} - -/** - * testTransform without computed data or collator arguments. - */ -void testTransform(const char* specStr, - const char* queryStr, - const char* objStr, - bool expectedStatusOK, - const char* expectedObjStr) { - testTransform(specStr, queryStr, objStr, nullptr, nullptr, expectedStatusOK, expectedObjStr); -} - -/** - * Test function to verify the results of projecting the $meta sortKey while under a covered - * projection. In particular, it tests that ProjectionExec can take a WorkingSetMember in - * RID_AND_IDX state and use the sortKey along with the index data to generate the final output - * document. For SERVER-20117. - * - * sortKey - The sort key in BSONObj form. - * projSpec - The JSON representation of the proj spec BSONObj. - * ikd - The data stored in the index. - * - * Returns the BSON representation of the actual output, to be checked against the expected output. - */ -BSONObj transformMetaSortKeyCovered(const BSONObj& sortKey, - const char* projSpec, - const IndexKeyDatum& ikd) { - WorkingSet ws; - WorkingSetID wsid = ws.allocate(); - WorkingSetMember* wsm = ws.get(wsid); - wsm->keyData.push_back(ikd); - wsm->addComputed(new SortKeyComputedData(sortKey)); - ws.transitionToRecordIdAndIdx(wsid); - + std::unique_ptr<MatchExpression> queryExpression = parseMatchExpression(query); QueryTestServiceContext serviceCtx; auto opCtx = serviceCtx.makeOperationContext(); - ProjectionExec projExec(opCtx.get(), fromjson(projSpec), nullptr, nullptr); - ASSERT_OK(projExec.transform(wsm)); - - return wsm->obj.value(); -} - -BSONObj transformCovered(BSONObj projSpec, const IndexKeyDatum& ikd) { - WorkingSet ws; - WorkingSetID wsid = ws.allocate(); - WorkingSetMember* wsm = ws.get(wsid); - wsm->keyData.push_back(ikd); - ws.transitionToRecordIdAndIdx(wsid); - - QueryTestServiceContext serviceCtx; - auto opCtx = serviceCtx.makeOperationContext(); - ProjectionExec projExec(opCtx.get(), projSpec, nullptr, nullptr); - ASSERT_OK(projExec.transform(wsm)); - - return wsm->obj.value(); + ProjectionExec exec(opCtx.get(), spec, queryExpression.get(), collator.get_ptr()); + + auto objStr = stdx::get_if<const char*>(&objStrOrDatum); + auto projected = objStr + ? exec.project(fromjson(*objStr), boost::none, BSONObj(), sortKey, textScore) + : exec.projectCovered({stdx::get<const IndexKeyDatum>(objStrOrDatum)}, + boost::none, + BSONObj(), + sortKey, + textScore); + + if (!projected.isOK()) + return boost::none; + else + return boost::make_optional(projected.getValue().toString()); } // @@ -200,13 +101,15 @@ BSONObj transformCovered(BSONObj projSpec, const IndexKeyDatum& ikd) { TEST(ProjectionExecTest, TransformPositionalDollar) { // Valid position $ projections. - testTransform("{'a.$': 1}", "{a: 10}", "{a: [10, 20, 30]}", true, "{a: [10]}"); - testTransform("{'a.$': 1}", "{a: 20}", "{a: [10, 20, 30]}", true, "{a: [20]}"); - testTransform("{'a.$': 1}", "{a: 30}", "{a: [10, 20, 30]}", true, "{a: [30]}"); - testTransform("{'a.$': 1}", "{a: {$gt: 4}}", "{a: [5]}", true, "{a: [5]}"); + ASSERT_EQ(boost::make_optional("{ a: [ 10 ] }"s), + project("{'a.$': 1}", "{a: 10}", "{a: [10, 20, 30]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 20 ] }"s), + project("{'a.$': 1}", "{a: 20}", "{a: [10, 20, 30]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 5 ] }"s), + project("{'a.$': 1}", "{a: {$gt: 4}}", "{a: [5]}")); // Invalid position $ projections. - testTransform("{'a.$': 1}", "{a: {$size: 1}}", "{a: [5]}", false, ""); + ASSERT_EQ(boost::none, project("{'a.$': 1}", "{a: {$size: 1}}", "{a: [5]}")); } // @@ -217,24 +120,25 @@ TEST(ProjectionExecTest, TransformElemMatch) { const char* s = "{a: [{x: 1, y: 10}, {x: 1, y: 20}, {x: 2, y: 10}]}"; // Valid $elemMatch projections. - testTransform("{a: {$elemMatch: {x: 1}}}", "{}", s, true, "{a: [{x: 1, y: 10}]}"); - testTransform("{a: {$elemMatch: {x: 1, y: 20}}}", "{}", s, true, "{a: [{x: 1, y: 20}]}"); - testTransform("{a: {$elemMatch: {x: 2}}}", "{}", s, true, "{a: [{x: 2, y: 10}]}"); - testTransform("{a: {$elemMatch: {x: 3}}}", "{}", s, true, "{}"); + ASSERT_EQ(boost::make_optional("{ a: [ { x: 1, y: 10 } ] }"s), + project("{a: {$elemMatch: {x: 1}}}", "{}", s)); + ASSERT_EQ(boost::make_optional("{ a: [ { x: 1, y: 20 } ] }"s), + project("{a: {$elemMatch: {x: 1, y: 20}}}", "{}", s)); + ASSERT_EQ(boost::make_optional("{ a: [ { x: 2, y: 10 } ] }"s), + project("{a: {$elemMatch: {x: 2}}}", "{}", s)); + ASSERT_EQ(boost::make_optional("{}"s), project("{a: {$elemMatch: {x: 3}}}", "{}", s)); // $elemMatch on unknown field z - testTransform("{a: {$elemMatch: {z: 1}}}", "{}", s, true, "{}"); + ASSERT_EQ(boost::make_optional("{}"s), project("{a: {$elemMatch: {z: 1}}}", "{}", s)); } TEST(ProjectionExecTest, ElemMatchProjectionRespectsCollator) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - testTransform("{a: {$elemMatch: {$gte: 'abc'}}}", - "{}", - "{a: ['zaa', 'zbb', 'zdd', 'zee']}", - nullptr, // WSM computed data - &collator, - true, - "{a: ['zdd']}"); + ASSERT_EQ(boost::make_optional("{ a: [ \"zdd\" ] }"s), + project("{a: {$elemMatch: {$gte: 'abc'}}}", + "{}", + "{a: ['zaa', 'zbb', 'zdd', 'zee']}", + collator)); } // @@ -243,27 +147,42 @@ TEST(ProjectionExecTest, ElemMatchProjectionRespectsCollator) { TEST(ProjectionExecTest, TransformSliceCount) { // Valid $slice projections using format {$slice: count}. - testTransform("{a: {$slice: -10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); - testTransform("{a: {$slice: -3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); - testTransform("{a: {$slice: -1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}"); - testTransform("{a: {$slice: 0}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}"); - testTransform("{a: {$slice: 1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}"); - testTransform("{a: {$slice: 3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); - testTransform("{a: {$slice: 10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: -10}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: -3}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 8 ] }"s), + project("{a: {$slice: -1}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [] }"s), + project("{a: {$slice: 0}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4 ] }"s), + project("{a: {$slice: 1}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: 3}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: 10}}", "{}", "{a: [4, 6, 8]}")); } TEST(ProjectionExecTest, TransformSliceSkipLimit) { // Valid $slice projections using format {$slice: [skip, limit]}. // Non-positive limits are rejected at the query parser and therefore not handled by // the projection execution stage. In fact, it will abort on an invalid limit. - testTransform("{a: {$slice: [-10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); - testTransform("{a: {$slice: [-3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}"); - testTransform("{a: {$slice: [-1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}"); - testTransform("{a: {$slice: [0, 2]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6]}"); - testTransform("{a: {$slice: [0, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}"); - testTransform("{a: {$slice: [1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [6]}"); - testTransform("{a: {$slice: [3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}"); - testTransform("{a: {$slice: [10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}"); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: [-10, 10]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s), + project("{a: {$slice: [-3, 5]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 8 ] }"s), + project("{a: {$slice: [-1, 1]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4, 6 ] }"s), + project("{a: {$slice: [0, 2]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 4 ] }"s), + project("{a: {$slice: [0, 1]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [ 6 ] }"s), + project("{a: {$slice: [1, 1]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [] }"s), + project("{a: {$slice: [3, 5]}}", "{}", "{a: [4, 6, 8]}")); + ASSERT_EQ(boost::make_optional("{ a: [] }"s), + project("{a: {$slice: [10, 10]}}", "{}", "{a: [4, 6, 8]}")); } // @@ -271,19 +190,19 @@ TEST(ProjectionExecTest, TransformSliceSkipLimit) { // TEST(ProjectionExecTest, TransformCoveredDottedProjection) { - BSONObj projection = fromjson("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}"); BSONObj keyPattern = fromjson("{a: 1, 'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}"); BSONObj keyData = fromjson("{'': 1, '': 2, '': 3, '': 4, '': 5}"); - BSONObj result = transformCovered(projection, IndexKeyDatum(keyPattern, keyData, nullptr)); - ASSERT_BSONOBJ_EQ(result, fromjson("{b: {c: 2, d: 3, f: {g: 4, h: 5}}}")); + ASSERT_EQ(boost::make_optional("{ b: { c: 2, d: 3, f: { g: 4, h: 5 } } }"s), + project("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}", + "{}", + IndexKeyDatum(keyPattern, keyData, nullptr))); } TEST(ProjectionExecTest, TransformNonCoveredDottedProjection) { - testTransform("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}", - "{}", - "{a: 1, b: {c: 2, d: 3, f: {g: 4, h: 5}}}", - true, - "{b: {c: 2, d: 3, f: {g: 4, h: 5}}}"); + ASSERT_EQ(boost::make_optional("{ b: { c: 2, d: 3, f: { g: 4, h: 5 } } }"s), + project("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}", + "{}", + "{a: 1, b: {c: 2, d: 3, f: {g: 4, h: 5}}}")); } // @@ -293,97 +212,97 @@ TEST(ProjectionExecTest, TransformNonCoveredDottedProjection) { TEST(ProjectionExecTest, TransformMetaTextScore) { // Query {} is ignored. - testTransform("{b: {$meta: 'textScore'}}", - "{}", - "{a: 'hello'}", - new mongo::TextScoreComputedData(100), - nullptr, // collator - true, - "{a: 'hello', b: 100}"); + ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: 100.0 }"s), + project("{b: {$meta: 'textScore'}}", + "{}", + "{a: 'hello'}", + boost::none, // collator + BSONObj(), // sortKey + 100.0)); // textScore // Projected meta field should overwrite existing field. - testTransform("{b: {$meta: 'textScore'}}", - "{}", - "{a: 'hello', b: -1}", - new mongo::TextScoreComputedData(100), - nullptr, // collator - true, - "{a: 'hello', b: 100}"); + ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: 100.0 }"s), + project("{b: {$meta: 'textScore'}}", + "{}", + "{a: 'hello', b: -1}", + boost::none, // collator + BSONObj(), // sortKey + 100.0)); // textScore } TEST(ProjectionExecTest, TransformMetaSortKey) { - testTransform("{b: {$meta: 'sortKey'}}", - "{}", - "{a: 'hello'}", - new mongo::SortKeyComputedData(BSON("" << 99)), - nullptr, // collator - true, - "{a: 'hello', b: {'': 99}}"); + // Query {} is ignored. + ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: { : 99 } }"s), + project("{b: {$meta: 'sortKey'}}", + "{}", + "{a: 'hello'}", + boost::none, // collator + BSON("" << 99))); // sortKey // Projected meta field should overwrite existing field. - testTransform("{a: {$meta: 'sortKey'}}", - "{}", - "{a: 'hello'}", - new mongo::SortKeyComputedData(BSON("" << 99)), - nullptr, // collator - true, - "{a: {'': 99}}"); + ASSERT_EQ(boost::make_optional("{ a: { : 99 } }"s), + project("{a: {$meta: 'sortKey'}}", + "{}", + "{a: 'hello'}", + boost::none, // collator + BSON("" << 99))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredNormal) { - BSONObj actualOut = - transformMetaSortKeyCovered(BSON("" << 5), - "{_id: 0, a: 1, b: {$meta: 'sortKey'}}", - IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr)); - BSONObj expectedOut = BSON("a" << 5 << "b" << BSON("" << 5)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ a: 5, b: { : 5 } }"s), + project("{_id: 0, a: 1, b: {$meta: 'sortKey'}}", + "{}", + IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr), + boost::none, // collator + BSON("" << 5))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredOverwrite) { - BSONObj actualOut = - transformMetaSortKeyCovered(BSON("" << 5), - "{_id: 0, a: 1, a: {$meta: 'sortKey'}}", - IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr)); - BSONObj expectedOut = BSON("a" << BSON("" << 5)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ a: { : 5 } }"s), + project("{_id: 0, a: 1, a: {$meta: 'sortKey'}}", + "{}", + IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr), + boost::none, // collator + BSON("" << 5))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredAdditionalData) { - BSONObj actualOut = transformMetaSortKeyCovered( - BSON("" << 5), - "{_id: 0, a: 1, b: {$meta: 'sortKey'}, c: 1}", - IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr)); - BSONObj expectedOut = BSON("a" << 5 << "c" << 6 << "b" << BSON("" << 5)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ a: 5, c: 6, b: { : 5 } }"s), + project("{_id: 0, a: 1, b: {$meta: 'sortKey'}, c: 1}", + "{}", + IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr), + boost::none, // collator + BSON("" << 5))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound) { - BSONObj actualOut = transformMetaSortKeyCovered( - BSON("" << 5 << "" << 6), - "{_id: 0, a: 1, b: {$meta: 'sortKey'}}", - IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr)); - BSONObj expectedOut = BSON("a" << 5 << "b" << BSON("" << 5 << "" << 6)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ a: 5, b: { : 5, : 6 } }"s), + project("{_id: 0, a: 1, b: {$meta: 'sortKey'}}", + "{}", + IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr), + boost::none, // collator + BSON("" << 5 << "" << 6))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound2) { - BSONObj actualOut = transformMetaSortKeyCovered( - BSON("" << 5 << "" << 6), - "{_id: 0, a: 1, c: 1, b: {$meta: 'sortKey'}}", - IndexKeyDatum( - BSON("a" << 1 << "b" << 1 << "c" << 1), BSON("" << 5 << "" << 6 << "" << 4), nullptr)); - BSONObj expectedOut = BSON("a" << 5 << "c" << 4 << "b" << BSON("" << 5 << "" << 6)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ a: 5, c: 4, b: { : 5, : 6 } }"s), + project("{_id: 0, a: 1, c: 1, b: {$meta: 'sortKey'}}", + "{}", + IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1), + BSON("" << 5 << "" << 6 << "" << 4), + nullptr), + boost::none, // collator + BSON("" << 5 << "" << 6))); // sortKey } TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound3) { - BSONObj actualOut = transformMetaSortKeyCovered( - BSON("" << 6 << "" << 4), - "{_id: 0, c: 1, d: 1, b: {$meta: 'sortKey'}}", - IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1), - BSON("" << 5 << "" << 6 << "" << 4 << "" << 9000), - nullptr)); - BSONObj expectedOut = BSON("c" << 4 << "d" << 9000 << "b" << BSON("" << 6 << "" << 4)); - ASSERT_BSONOBJ_EQ(actualOut, expectedOut); + ASSERT_EQ(boost::make_optional("{ c: 4, d: 9000, b: { : 6, : 4 } }"s), + project("{_id: 0, c: 1, d: 1, b: {$meta: 'sortKey'}}", + "{}", + IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1), + BSON("" << 5 << "" << 6 << "" << 4 << "" << 9000), + nullptr), + boost::none, // collator + BSON("" << 6 << "" << 4))); // sortKey } } // namespace diff --git a/src/mongo/db/exec/working_set.cpp b/src/mongo/db/exec/working_set.cpp index 893f39cb23b..bbf7012f6ce 100644 --- a/src/mongo/db/exec/working_set.cpp +++ b/src/mongo/db/exec/working_set.cpp @@ -186,23 +186,12 @@ bool WorkingSetMember::getFieldDotted(const string& field, BSONElement* out) con } // Our state should be such that we have index data/are covered. - for (size_t i = 0; i < keyData.size(); ++i) { - BSONObjIterator keyPatternIt(keyData[i].indexKeyPattern); - BSONObjIterator keyDataIt(keyData[i].keyData); - - while (keyPatternIt.more()) { - BSONElement keyPatternElt = keyPatternIt.next(); - verify(keyDataIt.more()); - BSONElement keyDataElt = keyDataIt.next(); - - if (field == keyPatternElt.fieldName()) { - *out = keyDataElt; - return true; - } - } + if (auto outOpt = IndexKeyDatum::getFieldDotted(keyData, field)) { + *out = outOpt.get(); + return true; + } else { + return false; } - - return false; } size_t WorkingSetMember::getMemUsage() const { diff --git a/src/mongo/db/exec/working_set.h b/src/mongo/db/exec/working_set.h index 00c7bfe565e..a081f8dbed4 100644 --- a/src/mongo/db/exec/working_set.h +++ b/src/mongo/db/exec/working_set.h @@ -30,6 +30,7 @@ #pragma once +#include "boost/optional.hpp" #include <vector> #include "mongo/base/disallow_copying.h" @@ -148,6 +149,29 @@ struct IndexKeyDatum { IndexKeyDatum(const BSONObj& keyPattern, const BSONObj& key, const IndexAccessMethod* index) : indexKeyPattern(keyPattern), keyData(key), index(index) {} + /** + * getFieldDotted produces the field with the provided name based on index keyData. The return + * object is populated if the element is in a provided index key. Returns none otherwise. + * Returning none indicates a query planning error. + */ + static boost::optional<BSONElement> getFieldDotted(const std::vector<IndexKeyDatum>& keyData, + const std::string& field) { + for (size_t i = 0; i < keyData.size(); ++i) { + BSONObjIterator keyPatternIt(keyData[i].indexKeyPattern); + BSONObjIterator keyDataIt(keyData[i].keyData); + + while (keyPatternIt.more()) { + BSONElement keyPatternElt = keyPatternIt.next(); + verify(keyDataIt.more()); + BSONElement keyDataElt = keyDataIt.next(); + + if (field == keyPatternElt.fieldName()) + return boost::make_optional(keyDataElt); + } + } + return boost::none; + } + // This is not owned and points into the IndexDescriptor's data. BSONObj indexKeyPattern; diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index b9b1776f426..2bb817d1699 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -377,10 +377,7 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, // There might be a projection. The idhack stage will always fetch the full // document, so we don't support covered projections. However, we might use the // simple inclusion fast path. - if (NULL != canonicalQuery->getProj()) { - ProjectionStageParams params; - params.projObj = canonicalQuery->getProj()->getProjObj(); - params.collator = canonicalQuery->getCollator(); + if (nullptr != canonicalQuery->getProj()) { // Add a SortKeyGeneratorStage if there is a $meta sortKey projection. if (canonicalQuery->getProj()->wantSortKey()) { @@ -397,13 +394,16 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, canonicalQuery->getProj()->wantIndexKey() || canonicalQuery->getProj()->wantSortKey() || canonicalQuery->getProj()->hasDottedFieldPath()) { - params.fullExpression = canonicalQuery->root(); - params.projImpl = ProjectionStageParams::NO_FAST_PATH; + root = make_unique<ProjectionStageDefault>(opCtx, + canonicalQuery->getProj()->getProjObj(), + ws, + std::move(root), + canonicalQuery->root(), + canonicalQuery->getCollator()); } else { - params.projImpl = ProjectionStageParams::SIMPLE_DOC; + root = make_unique<ProjectionStageSimple>( + opCtx, canonicalQuery->getProj()->getProjObj(), ws, std::move(root)); } - - root = make_unique<ProjectionStage>(opCtx, params, ws, root.release()); } return PrepareExecutionResult(std::move(canonicalQuery), nullptr, std::move(root)); @@ -788,11 +788,12 @@ StatusWith<unique_ptr<PlanStage>> applyProjection(OperationContext* opCtx, "Cannot use a $meta sortKey projection in findAndModify commands."}; } - ProjectionStageParams params; - params.projObj = proj; - params.collator = cq->getCollator(); - params.fullExpression = cq->root(); - return {make_unique<ProjectionStage>(opCtx, params, ws, root.release())}; + return {make_unique<ProjectionStageDefault>(opCtx, + proj, + ws, + std::unique_ptr<PlanStage>(root.release()), + cq->root(), + cq->getCollator())}; } } // namespace diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index 31a7ff7a8ab..eb3a5354fcb 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -140,29 +140,28 @@ PlanStage* buildStages(OperationContext* opCtx, } case STAGE_PROJECTION: { const ProjectionNode* pn = static_cast<const ProjectionNode*>(root); - PlanStage* childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws); + unique_ptr<PlanStage> childStage{ + buildStages(opCtx, collection, cq, qsol, pn->children[0], ws)}; if (nullptr == childStage) { return nullptr; } - ProjectionStageParams params; - params.projObj = pn->projection; - params.collator = cq.getCollator(); - - // Stuff the right data into the params depending on what proj impl we use. - if (ProjectionNode::DEFAULT == pn->projType) { - params.fullExpression = pn->fullExpression; - params.projImpl = ProjectionStageParams::NO_FAST_PATH; - } else if (ProjectionNode::COVERED_ONE_INDEX == pn->projType) { - params.projImpl = ProjectionStageParams::COVERED_ONE_INDEX; - params.coveredKeyObj = pn->coveredKeyObj; - invariant(!pn->coveredKeyObj.isEmpty()); - } else { - invariant(ProjectionNode::SIMPLE_DOC == pn->projType); - params.projImpl = ProjectionStageParams::SIMPLE_DOC; + switch (pn->projType) { + case ProjectionNode::DEFAULT: + return new ProjectionStageDefault(opCtx, + pn->projection, + ws, + std::move(childStage), + pn->fullExpression, + cq.getCollator()); + case ProjectionNode::COVERED_ONE_INDEX: + invariant(!pn->coveredKeyObj.isEmpty()); + return new ProjectionStageCovered( + opCtx, pn->projection, ws, std::move(childStage), pn->coveredKeyObj); + case ProjectionNode::SIMPLE_DOC: + return new ProjectionStageSimple( + opCtx, pn->projection, ws, std::move(childStage)); } - - return new ProjectionStage(opCtx, params, ws, childStage); } case STAGE_LIMIT: { const LimitNode* ln = static_cast<const LimitNode*>(root); |