diff options
author | Jacob Evans <jacob.evans@10gen.com> | 2019-01-04 13:15:58 -0500 |
---|---|---|
committer | Jacob Evans <jacob.evans@10gen.com> | 2019-01-29 13:53:10 -0500 |
commit | 41557fac170a905cb7b0cca7564e4c0ec48bbefd (patch) | |
tree | 5dc37a78b02e03ceefa34f75abc13b5062e17a0e /src/mongo | |
parent | b4ee2a119c65d1f2a0cb2288c1242fbf4f74a543 (diff) | |
download | mongo-41557fac170a905cb7b0cca7564e4c0ec48bbefd.tar.gz |
SERVER-38695 Make QuerySolutionNode subclasses for projection fast-paths
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/exec/delete.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/exec/projection.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/exec/projection.h | 30 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec.h | 6 | ||||
-rw-r--r-- | src/mongo/db/exec/update.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/query/explain.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/query/plan_ranker.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/query/planner_analysis.cpp | 213 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_test_lib.cpp | 24 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.cpp | 50 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.h | 135 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution_test.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 44 | ||||
-rw-r--r-- | src/mongo/db/query/stage_types.h | 6 |
15 files changed, 402 insertions, 245 deletions
diff --git a/src/mongo/db/exec/delete.cpp b/src/mongo/db/exec/delete.cpp index 71e6f6a5628..e71ec2adcaf 100644 --- a/src/mongo/db/exec/delete.cpp +++ b/src/mongo/db/exec/delete.cpp @@ -294,16 +294,21 @@ long long DeleteStage::getNumDeleted(const PlanExecutor& exec) { // If the collection exists, the delete plan may either have a delete stage at the root, or (for // findAndModify) a projection stage wrapping a delete stage. - if (StageType::STAGE_PROJECTION == exec.getRootStage()->stageType()) { - invariant(exec.getRootStage()->getChildren().size() == 1U); - invariant(StageType::STAGE_DELETE == exec.getRootStage()->child()->stageType()); - const SpecificStats* stats = exec.getRootStage()->child()->getSpecificStats(); - return static_cast<const DeleteStats*>(stats)->docsDeleted; - } else { - invariant(StageType::STAGE_DELETE == exec.getRootStage()->stageType()); - const auto* deleteStats = - static_cast<const DeleteStats*>(exec.getRootStage()->getSpecificStats()); - return deleteStats->docsDeleted; + switch (exec.getRootStage()->stageType()) { + case StageType::STAGE_PROJECTION_DEFAULT: + case StageType::STAGE_PROJECTION_COVERED: + case StageType::STAGE_PROJECTION_SIMPLE: { + invariant(exec.getRootStage()->getChildren().size() == 1U); + invariant(StageType::STAGE_DELETE == exec.getRootStage()->child()->stageType()); + const SpecificStats* stats = exec.getRootStage()->child()->getSpecificStats(); + return static_cast<const DeleteStats*>(stats)->docsDeleted; + } + default: { + invariant(StageType::STAGE_DELETE == exec.getRootStage()->stageType()); + const auto* deleteStats = + static_cast<const DeleteStats*>(exec.getRootStage()->getSpecificStats()); + return deleteStats->docsDeleted; + } } } diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp index 18debacd536..0a559fb2a1c 100644 --- a/src/mongo/db/exec/projection.cpp +++ b/src/mongo/db/exec/projection.cpp @@ -121,8 +121,9 @@ StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, ProjectionStage::ProjectionStage(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, - std::unique_ptr<PlanStage> child) - : PlanStage(opCtx, std::move(child), kStageType), _projObj(projObj), _ws(*ws) {} + std::unique_ptr<PlanStage> child, + const char* stageType) + : PlanStage(opCtx, std::move(child), stageType), _projObj(projObj), _ws(*ws) {} // static void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields) { @@ -183,7 +184,7 @@ PlanStage::StageState ProjectionStage::doWork(WorkingSetID* out) { std::unique_ptr<PlanStageStats> ProjectionStage::getStats() { _commonStats.isEOF = isEOF(); - auto ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_PROJECTION); + auto ret = std::make_unique<PlanStageStats>(_commonStats, stageType()); auto projStats = std::make_unique<ProjectionStats>(_specificStats); projStats->projObj = _projObj; @@ -197,10 +198,10 @@ ProjectionStageDefault::ProjectionStageDefault(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, std::unique_ptr<PlanStage> child, - const MatchExpression* fullExpression, + const MatchExpression& fullExpression, const CollatorInterface* collator) - : ProjectionStage(opCtx, projObj, ws, std::move(child)), - _exec(opCtx, projObj, fullExpression, collator) {} + : ProjectionStage(opCtx, projObj, ws, std::move(child), "PROJECTION_DEFAULT"), + _exec(opCtx, projObj, &fullExpression, collator) {} Status ProjectionStageDefault::transform(WorkingSetMember* member) const { // The default no-fast-path case. @@ -234,7 +235,8 @@ ProjectionStageCovered::ProjectionStageCovered(OperationContext* opCtx, WorkingSet* ws, std::unique_ptr<PlanStage> child, const BSONObj& coveredKeyObj) - : ProjectionStage(opCtx, projObj, ws, std::move(child)), _coveredKeyObj(coveredKeyObj) { + : ProjectionStage(opCtx, projObj, ws, std::move(child), "PROJECTION_COVERED"), + _coveredKeyObj(coveredKeyObj) { invariant(projObjHasOwnedData()); // Figure out what fields are in the projection. getSimpleInclusionFields(_projObj, &_includedFields); @@ -289,7 +291,7 @@ ProjectionStageSimple::ProjectionStageSimple(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, std::unique_ptr<PlanStage> child) - : ProjectionStage(opCtx, projObj, ws, std::move(child)) { + : ProjectionStage(opCtx, projObj, ws, std::move(child), "PROJECTION_SIMPLE") { invariant(projObjHasOwnedData()); // Figure out what fields are in the projection. getSimpleInclusionFields(_projObj, &_includedFields); diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h index b28cdcd9302..e229d12a219 100644 --- a/src/mongo/db/exec/projection.h +++ b/src/mongo/db/exec/projection.h @@ -30,7 +30,6 @@ #pragma once - #include "mongo/db/exec/plan_stage.h" #include "mongo/db/exec/projection_exec.h" #include "mongo/db/jsobj.h" @@ -50,18 +49,13 @@ protected: ProjectionStage(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, - std::unique_ptr<PlanStage> child); + std::unique_ptr<PlanStage> child, + const char* stageType); public: - static constexpr const char* kStageType = "PROJECTION"; - bool isEOF() final; StageState doWork(WorkingSetID* out) final; - StageType stageType() const final { - return STAGE_PROJECTION; - } - std::unique_ptr<PlanStageStats> getStats() final; const SpecificStats* getSpecificStats() const final { @@ -106,15 +100,19 @@ private: class ProjectionStageDefault final : public ProjectionStage { public: /** - * ProjectionNode::DEFAULT should use this for construction. + * ProjectionNodeDefault should use this for construction. */ ProjectionStageDefault(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, std::unique_ptr<PlanStage> child, - const MatchExpression* fullExpression, + const MatchExpression& fullExpression, const CollatorInterface* collator); + StageType stageType() const final { + return STAGE_PROJECTION_DEFAULT; + } + private: Status transform(WorkingSetMember* member) const final; @@ -130,7 +128,7 @@ private: class ProjectionStageCovered final : public ProjectionStage { public: /** - * ProjectionNode::COVERED_ONE_INDEX should obtain a fast-path object through this constructor. + * ProjectionNodeCovered should obtain a fast-path object through this constructor. */ ProjectionStageCovered(OperationContext* opCtx, const BSONObj& projObj, @@ -138,6 +136,10 @@ public: std::unique_ptr<PlanStage> child, const BSONObj& coveredKeyObj); + StageType stageType() const final { + return STAGE_PROJECTION_COVERED; + } + private: Status transform(WorkingSetMember* member) const final; @@ -164,13 +166,17 @@ private: class ProjectionStageSimple final : public ProjectionStage { public: /** - * ProjectionNode::SIMPLE_DOC should obtain a fast-path object through this constructor. + * ProjectionNodeSimple should obtain a fast-path object through this constructor. */ ProjectionStageSimple(OperationContext* opCtx, const BSONObj& projObj, WorkingSet* ws, std::unique_ptr<PlanStage> child); + StageType stageType() const final { + return STAGE_PROJECTION_SIMPLE; + } + private: Status transform(WorkingSetMember* member) const final; diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h index 4d79808b0d7..e330184f3a7 100644 --- a/src/mongo/db/exec/projection_exec.h +++ b/src/mongo/db/exec/projection_exec.h @@ -141,9 +141,9 @@ public: 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. + * Performs a projection given index 'KeyData' to directly retrieve results. This function + * handles projections which do not qualify for the ProjectionNodeCovered fast-path but are + * still covered by indices. */ StatusWith<BSONObj> projectCovered( const std::vector<IndexKeyDatum>& keyData, diff --git a/src/mongo/db/exec/update.cpp b/src/mongo/db/exec/update.cpp index 0dfaacddc87..1aa07aa2bff 100644 --- a/src/mongo/db/exec/update.cpp +++ b/src/mongo/db/exec/update.cpp @@ -831,14 +831,18 @@ const UpdateStats* UpdateStage::getUpdateStats(const PlanExecutor* exec) { // If the collection exists, then we expect the root of the plan tree to either be an update // stage, or (for findAndModify) a projection stage wrapping an update stage. - if (StageType::STAGE_PROJECTION == exec->getRootStage()->stageType()) { - invariant(exec->getRootStage()->getChildren().size() == 1U); - invariant(StageType::STAGE_UPDATE == exec->getRootStage()->child()->stageType()); - const SpecificStats* stats = exec->getRootStage()->child()->getSpecificStats(); - return static_cast<const UpdateStats*>(stats); - } else { - invariant(StageType::STAGE_UPDATE == exec->getRootStage()->stageType()); - return static_cast<const UpdateStats*>(exec->getRootStage()->getSpecificStats()); + switch (exec->getRootStage()->stageType()) { + case StageType::STAGE_PROJECTION_DEFAULT: + case StageType::STAGE_PROJECTION_COVERED: + case StageType::STAGE_PROJECTION_SIMPLE: { + invariant(exec->getRootStage()->getChildren().size() == 1U); + invariant(StageType::STAGE_UPDATE == exec->getRootStage()->child()->stageType()); + const SpecificStats* stats = exec->getRootStage()->child()->getSpecificStats(); + return static_cast<const UpdateStats*>(stats); + } + default: + invariant(StageType::STAGE_UPDATE == exec->getRootStage()->stageType()); + return static_cast<const UpdateStats*>(exec->getRootStage()->getSpecificStats()); } } diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index ad299239b82..0e54916922d 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -512,7 +512,9 @@ void Explain::statsToBSON(const PlanStageStats& stats, } else if (STAGE_LIMIT == stats.stageType) { LimitStats* spec = static_cast<LimitStats*>(stats.specific.get()); bob->appendNumber("limitAmount", spec->limit); - } else if (STAGE_PROJECTION == stats.stageType) { + } else if (STAGE_PROJECTION_DEFAULT == stats.stageType || + STAGE_PROJECTION_COVERED == stats.stageType || + STAGE_PROJECTION_SIMPLE == stats.stageType) { ProjectionStats* spec = static_cast<ProjectionStats*>(stats.specific.get()); bob->append("transformBy", spec->projObj); } else if (STAGE_RECORD_STORE_FAST_COUNT == stats.stageType) { diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index c3bdcae3786..549d752d440 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -398,7 +398,7 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, canonicalQuery->getProj()->getProjObj(), ws, std::move(root), - canonicalQuery->root(), + *canonicalQuery->root(), canonicalQuery->getCollator()); } else { root = make_unique<ProjectionStageSimple>( @@ -792,7 +792,7 @@ StatusWith<unique_ptr<PlanStage>> applyProjection(OperationContext* opCtx, proj, ws, std::unique_ptr<PlanStage>(root.release()), - cq->root(), + *cq->root(), cq->getCollator())}; } @@ -1360,8 +1360,12 @@ bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, QuerySolutionNode* root = soln->root.get(); // Root stage must be a project. - if (STAGE_PROJECTION != root->getType()) { - return false; + switch (root->getType()) { + default: + return false; + case STAGE_PROJECTION_DEFAULT: + case STAGE_PROJECTION_COVERED: + case STAGE_PROJECTION_SIMPLE:; } // Child should be either an ixscan or fetch. @@ -1474,7 +1478,13 @@ bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, // If there is a fetch node, then there is no need for the projection. The fetch node should // become the new root, with the distinct as its child. The PROJECT=>FETCH=>IXSCAN tree // should become FETCH=>DISTINCT_SCAN. - invariant(STAGE_PROJECTION == root->getType()); + switch (root->getType()) { + default: + MONGO_UNREACHABLE; + case STAGE_PROJECTION_DEFAULT: + case STAGE_PROJECTION_COVERED: + case STAGE_PROJECTION_SIMPLE:; + } invariant(STAGE_FETCH == root->children[0]->getType()); invariant(STAGE_IXSCAN == root->children[0]->children[0]->getType()); @@ -1491,7 +1501,13 @@ bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, fetchNode->children[0] = distinctNode.release(); } else { // There is no fetch node. The PROJECT=>IXSCAN tree should become PROJECT=>DISTINCT_SCAN. - invariant(STAGE_PROJECTION == root->getType()); + switch (root->getType()) { + default: + MONGO_UNREACHABLE; + case STAGE_PROJECTION_DEFAULT: + case STAGE_PROJECTION_COVERED: + case STAGE_PROJECTION_SIMPLE:; + } invariant(STAGE_IXSCAN == root->children[0]->getType()); // Take ownership of the index scan node, detaching it from the solution tree. diff --git a/src/mongo/db/query/plan_ranker.cpp b/src/mongo/db/query/plan_ranker.cpp index a8260b22c7f..897e664e7f1 100644 --- a/src/mongo/db/query/plan_ranker.cpp +++ b/src/mongo/db/query/plan_ranker.cpp @@ -214,7 +214,9 @@ double PlanRanker::scoreTree(const PlanStageStats* stats) { // We only do this when we have a projection stage because we have so many jstests that // check bounds even when a collscan plan is just as good as the ixscan'd plan :( double noFetchBonus = epsilon; - if (hasStage(STAGE_PROJECTION, stats) && hasStage(STAGE_FETCH, stats)) { + if ((hasStage(STAGE_PROJECTION_DEFAULT, stats) || hasStage(STAGE_PROJECTION_COVERED, stats) || + hasStage(STAGE_PROJECTION_SIMPLE, stats)) && + hasStage(STAGE_FETCH, stats)) { noFetchBonus = 0; } diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index 0bbce034fcd..8cdc7d91f43 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -291,6 +291,112 @@ void geoSkipValidationOn(const std::set<StringData>& twoDSphereFields, } } +/** + * If any field is missing from the list of fields the projection wants, we are not covered. + */ +auto isCoveredOrAlreadyFetched(const vector<StringData>& fields, + const QuerySolutionNode& solnRoot) { + for (size_t i = 0; i < fields.size(); ++i) { + if (!solnRoot.hasField(fields[i].toString())) + return false; + } + return true; +} + +/** + * Checks all properties that exclude a projection from being simple. + */ +auto isSimpleProjection(const CanonicalQuery& query) { + return !query.getProj()->wantIndexKey() && !query.getProj()->wantSortKey() && + !query.getProj()->hasDottedFieldPath() && !query.getProj()->requiresDocument(); +} + +/** + * If 'solnRoot' is returning index key data from a single index, returns the associated index key + * pattern. Otherwise, returns an empty object. + */ +auto produceCoveredKeyObj(QuerySolutionNode* solnRoot) { + vector<QuerySolutionNode*> leafNodes; + getLeafNodes(solnRoot, &leafNodes); + + // Both the IXSCAN and DISTINCT stages provide covered key data. + if (1 == leafNodes.size()) { + if (STAGE_IXSCAN == leafNodes[0]->getType()) { + IndexScanNode* ixn = static_cast<IndexScanNode*>(leafNodes[0]); + return ixn->index.keyPattern; + } else if (STAGE_DISTINCT_SCAN == leafNodes[0]->getType()) { + DistinctNode* dn = static_cast<DistinctNode*>(leafNodes[0]); + return dn->index.keyPattern; + } + } + return BSONObj(); +} + +/** + * When projection needs to be added to the solution tree, this function chooses between the default + * implementation and one of the fast paths. + */ +std::unique_ptr<ProjectionNode> analyzeProjection(const CanonicalQuery& query, + std::unique_ptr<QuerySolutionNode> solnRoot, + const bool hasSortStage) { + 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. + auto addSortKeyGeneratorStageIfNeeded = [&]() { + if (!hasSortStage && query.getProj()->wantSortKey()) { + auto keyGenNode = std::make_unique<SortKeyGeneratorNode>(); + keyGenNode->sortSpec = qr.getSort(); + keyGenNode->children.push_back(solnRoot.release()); + solnRoot = std::move(keyGenNode); + } + }; + + LOG(5) << "PROJECTION: Current plan is:\n" << redact(solnRoot->toString()); + + // If the projection requires the entire document we add a fetch stage if not present. Otherwise + // we add a fetch stage if we are not covered and not returnKey. + if ((query.getProj()->requiresDocument() && !solnRoot->fetched()) || + (!isCoveredOrAlreadyFetched(query.getProj()->getRequiredFields(), *solnRoot) && + !query.getProj()->wantIndexKey())) { + auto fetch = std::make_unique<FetchNode>(); + fetch->children.push_back(solnRoot.release()); + solnRoot = std::move(fetch); + } + + // There are two projection fast paths available for simple inclusion projections that don't + // need an index key or sort key, don't have any dotted-path inclusions, and don't have the + // 'requiresDocument' property: the ProjectionNodeSimple fast-path for plans that have a fetch + // stage and the ProjectionNodeCovered for plans with an index scan that the projection can + // cover. Plans that don't meet all the requirements for these fast path projections will all + // use ProjectionNodeDefault, which is able to handle all projections, covered or otherwise. + if (isSimpleProjection(query)) { + // If the projection is simple, but not covered, use 'ProjectionNodeSimple'. + if (solnRoot->fetched()) { + addSortKeyGeneratorStageIfNeeded(); + return std::make_unique<ProjectionNodeSimple>( + std::move(solnRoot), *query.root(), qr.getProj(), *query.getProj()); + } else { + // If we're here we're not fetched so we're covered. Let's see if we can get out of + // using the default projType. If 'solnRoot' is an index scan we can use the faster + // covered impl. + BSONObj coveredKeyObj = produceCoveredKeyObj(solnRoot.get()); + if (!coveredKeyObj.isEmpty()) { + addSortKeyGeneratorStageIfNeeded(); + return std::make_unique<ProjectionNodeCovered>(std::move(solnRoot), + *query.root(), + qr.getProj(), + *query.getProj(), + std::move(coveredKeyObj)); + } + } + } + + addSortKeyGeneratorStageIfNeeded(); + return std::make_unique<ProjectionNodeDefault>( + std::move(solnRoot), *query.root(), qr.getProj(), *query.getProj()); +} + } // namespace // static @@ -620,7 +726,7 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( const CanonicalQuery& query, const QueryPlannerParams& params, std::unique_ptr<QuerySolutionNode> solnRoot) { - auto soln = stdx::make_unique<QuerySolution>(); + auto soln = std::make_unique<QuerySolution>(); soln->filterData = query.getQueryObj(); soln->indexFilterApplied = params.indexFiltersApplied; @@ -649,9 +755,9 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( } if (fetch) { - FetchNode* fetch = new FetchNode(); - fetch->children.push_back(solnRoot.release()); - solnRoot.reset(fetch); + FetchNode* fetchNode = new FetchNode(); + fetchNode->children.push_back(solnRoot.release()); + solnRoot.reset(fetchNode); } } @@ -665,7 +771,7 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( // This can happen if we need to create a blocking sort stage and we're not allowed to. if (!solnRoot) { - return NULL; + return nullptr; } // A solution can be blocking if it has a blocking sort stage or @@ -675,7 +781,6 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( const QueryRequest& qr = query.getQueryRequest(); - if (qr.getSkip()) { auto skip = std::make_unique<SkipNode>(); skip->skip = *qr.getSkip(); @@ -684,102 +789,12 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( } // Project the results. - if (NULL != query.getProj()) { - LOG(5) << "PROJECTION: Current plan is:\n" << redact(solnRoot->toString()); - - ProjectionNode::ProjectionType projType = ProjectionNode::DEFAULT; - BSONObj coveredKeyObj; - - if (query.getProj()->requiresDocument()) { - // If the projection requires the entire document, somebody must fetch. - if (!solnRoot->fetched()) { - FetchNode* fetch = new FetchNode(); - fetch->children.push_back(solnRoot.release()); - solnRoot.reset(fetch); - } - } else if (!query.getProj()->wantIndexKey()) { - // The only way we're here is if it's a simple inclusion projection. Often such - // simple projections are eligible for an optimized execution path. However, in some - // special cases (e.g. dotted paths or the presence of a sortKey $meta projection), we - // may have to fall back to the default execution path. - const vector<StringData>& fields = query.getProj()->getRequiredFields(); - bool covered = true; - for (size_t i = 0; i < fields.size(); ++i) { - if (!solnRoot->hasField(fields[i].toString())) { - covered = false; - break; - } - } - - // If any field is missing from the list of fields the projection wants, - // a fetch is required. - if (!covered) { - FetchNode* fetch = new FetchNode(); - fetch->children.push_back(solnRoot.release()); - solnRoot.reset(fetch); - - // It's simple but we'll have the full document and we should just iterate - // over that. - projType = ProjectionNode::SIMPLE_DOC; - } else { - if (solnRoot->fetched()) { - // Fetched implies hasObj() so let's run with that. - projType = ProjectionNode::SIMPLE_DOC; - } else { - // If we're here we're not fetched so we're covered. Let's see if we can - // get out of using the default projType. If there's only one leaf - // underneath and it's giving us index data we can use the faster covered - // impl. - vector<QuerySolutionNode*> leafNodes; - getLeafNodes(solnRoot.get(), &leafNodes); - - if (1 == leafNodes.size()) { - // Both the IXSCAN and DISTINCT stages provide covered key data. - if (STAGE_IXSCAN == leafNodes[0]->getType()) { - projType = ProjectionNode::COVERED_ONE_INDEX; - IndexScanNode* ixn = static_cast<IndexScanNode*>(leafNodes[0]); - coveredKeyObj = ixn->index.keyPattern; - } else if (STAGE_DISTINCT_SCAN == leafNodes[0]->getType()) { - projType = ProjectionNode::COVERED_ONE_INDEX; - DistinctNode* dn = static_cast<DistinctNode*>(leafNodes[0]); - coveredKeyObj = dn->index.keyPattern; - } - } - } - } - - // If we have a $meta sortKey, just use the project default path, as currently the - // project fast paths cannot handle $meta sortKey projections. - // - // Similarly, the fast paths cannot handle dotted field paths. - if (query.getProj()->wantSortKey() || query.getProj()->hasDottedFieldPath()) { - projType = ProjectionNode::DEFAULT; - } - } + if (query.getProj()) { + solnRoot = analyzeProjection(query, std::move(solnRoot), hasSortStage); // If we don't have a covered project, and we're not allowed to put an uncovered one in, // bail out. - if (solnRoot->fetched() && - (params.options & QueryPlannerParams::NO_UNCOVERED_PROJECTIONS)) { + if (solnRoot->fetched() && params.options & QueryPlannerParams::NO_UNCOVERED_PROJECTIONS) return nullptr; - } - - // 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. - if (!hasSortStage && query.getProj()->wantSortKey()) { - SortKeyGeneratorNode* keyGenNode = new SortKeyGeneratorNode(); - keyGenNode->sortSpec = qr.getSort(); - keyGenNode->children.push_back(solnRoot.release()); - solnRoot.reset(keyGenNode); - } - - // We now know we have whatever data is required for the projection. - ProjectionNode* projNode = new ProjectionNode(*query.getProj()); - projNode->children.push_back(solnRoot.release()); - projNode->fullExpression = query.root(); - projNode->projection = qr.getProj(); - projNode->projType = projType; - projNode->coveredKeyObj = coveredKeyObj; - solnRoot.reset(projNode); } else { // If there's no projection, we must fetch, as the user wants the entire doc. if (!solnRoot->fetched() && !(params.options & QueryPlannerParams::IS_COUNT)) { diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index dc57f0c00fc..8cc616dbbc0 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -576,7 +576,9 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, } return childrenMatch(andSortedObj, asn, relaxBoundsCheck); - } else if (STAGE_PROJECTION == trueSoln->getType()) { + } else if (STAGE_PROJECTION_DEFAULT == trueSoln->getType() || + STAGE_PROJECTION_COVERED == trueSoln->getType() || + STAGE_PROJECTION_SIMPLE == trueSoln->getType()) { const ProjectionNode* pn = static_cast<const ProjectionNode*>(trueSoln); BSONElement el = testSoln["proj"]; @@ -589,11 +591,21 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, BSONElement projType = projObj["type"]; if (!projType.eoo()) { string projTypeStr = projType.str(); - if (!((pn->projType == ProjectionNode::DEFAULT && projTypeStr == "default") || - (pn->projType == ProjectionNode::SIMPLE_DOC && projTypeStr == "simple") || - (pn->projType == ProjectionNode::COVERED_ONE_INDEX && - projTypeStr == "coveredIndex"))) { - return false; + switch (pn->getType()) { + case StageType::STAGE_PROJECTION_DEFAULT: + if (projTypeStr != "default") + return false; + break; + case StageType::STAGE_PROJECTION_COVERED: + if (projTypeStr != "coveredIndex") + return false; + break; + case StageType::STAGE_PROJECTION_SIMPLE: + if (projTypeStr != "simple") + return false; + break; + default: + MONGO_UNREACHABLE; } } diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index 2dc730a4f5a..61ca12f60e5 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -858,14 +858,7 @@ void ProjectionNode::appendToString(mongoutils::str::stream* ss, int indent) con addIndent(ss, indent + 1); *ss << "proj = " << projection.toString() << '\n'; addIndent(ss, indent + 1); - if (DEFAULT == projType) { - *ss << "type = DEFAULT\n"; - } else if (COVERED_ONE_INDEX == projType) { - *ss << "type = COVERED_ONE_INDEX\n"; - } else { - invariant(SIMPLE_DOC == projType); - *ss << "type = SIMPLE_DOC\n"; - } + *ss << "type = " << projectionImplementationTypeToString() << '\n'; addCommon(ss, indent); addIndent(ss, indent + 1); *ss << "Child:" << '\n'; @@ -896,21 +889,46 @@ void ProjectionNode::computeProperties() { } } -QuerySolutionNode* ProjectionNode::clone() const { - ProjectionNode* copy = new ProjectionNode(parsed); - cloneBaseData(copy); +void ProjectionNode::cloneProjectionData(ProjectionNode* copy) const { + // ProjectionNode should not populate filter. This should be a no-op. + if (this->filter) + copy->filter = this->filter->shallowClone(); copy->_sorts = this->_sorts; +} - // This MatchExpression* is owned by the canonical query, not by the - // ProjectionNode. Just copying the pointer is fine. - copy->fullExpression = this->fullExpression; +ProjectionNode* ProjectionNodeDefault::clone() const { + auto copy = std::make_unique<ProjectionNodeDefault>( + std::unique_ptr<QuerySolutionNode>(children[0]->clone()), + fullExpression, + projection, + parsed); + ProjectionNode::cloneProjectionData(copy.get()); + return copy.release(); +} - copy->projection = this->projection; +ProjectionNode* ProjectionNodeCovered::clone() const { + auto copy = std::make_unique<ProjectionNodeCovered>( + std::unique_ptr<QuerySolutionNode>(children[0]->clone()), + fullExpression, + projection, + parsed, + coveredKeyObj); + ProjectionNode::cloneProjectionData(copy.get()); + return copy.release(); +} - return copy; +ProjectionNode* ProjectionNodeSimple::clone() const { + auto copy = std::make_unique<ProjectionNodeSimple>( + std::unique_ptr<QuerySolutionNode>(children[0]->clone()), + fullExpression, + projection, + parsed); + ProjectionNode::cloneProjectionData(copy.get()); + return copy.release(); } + // // SortKeyGeneratorNode // diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h index 491d6d99e5b..4e4d724b555 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -32,6 +32,7 @@ #include <memory> +#include "mongo/base/string_data.h" #include "mongo/bson/bsonobj_comparator_interface.h" #include "mongo/db/fts/fts_query.h" #include "mongo/db/jsobj.h" @@ -50,6 +51,12 @@ class GeoNearExpression; */ struct QuerySolutionNode { QuerySolutionNode() {} + + /** + * Constructs a QuerySolutionNode with a single child. + */ + QuerySolutionNode(std::unique_ptr<QuerySolutionNode> child) : children{child.release()} {} + virtual ~QuerySolutionNode() { for (size_t i = 0; i < children.size(); ++i) { delete children[i]; @@ -520,40 +527,26 @@ struct IndexScanNode : public QuerySolutionNode { std::set<StringData> multikeyFields; }; -struct ProjectionNode : public QuerySolutionNode { - /** - * We have a few implementations of the projection functionality. The most general - * implementation 'DEFAULT' is much slower than the fast-path implementations - * below. We only really have all the information available to choose a projection - * implementation at planning time. - */ - enum ProjectionType { - // This is the most general implementation of the projection functionality. It handles - // every case. - DEFAULT, - - // This is a fast-path for when the projection is fully covered by one index. - COVERED_ONE_INDEX, - - // This is a fast-path for when the projection only has inclusions on non-dotted fields. - SIMPLE_DOC, - }; - - ProjectionNode(ParsedProjection proj) - : _sorts(SimpleBSONObjComparator::kInstance.makeBSONObjSet()), - fullExpression(NULL), - projType(DEFAULT), - parsed(proj) {} - - virtual ~ProjectionNode() {} - - virtual StageType getType() const { - return STAGE_PROJECTION; - } - - virtual void computeProperties(); +/** + * We have a few implementations of the projection functionality. They are chosen by constructing + * a type derived from this abstract struct. The most general implementation 'ProjectionNodeDefault' + * is much slower than the fast-path implementations. We only really have all the information + * available to choose a projection implementation at planning time. + */ +struct ProjectionNode : QuerySolutionNode { + ProjectionNode(std::unique_ptr<QuerySolutionNode> child, + const MatchExpression& fullExpression, + BSONObj projection, + ParsedProjection parsed) + : QuerySolutionNode(std::move(child)), + _sorts(SimpleBSONObjComparator::kInstance.makeBSONObjSet()), + fullExpression(fullExpression), + projection(std::move(projection)), + parsed(parsed) {} + + void computeProperties() final; - virtual void appendToString(mongoutils::str::stream* ss, int indent) const; + void appendToString(mongoutils::str::stream* ss, int indent) const final; /** * Data from the projection node is considered fetch iff the child provides fetched data. @@ -584,29 +577,89 @@ struct ProjectionNode : public QuerySolutionNode { return _sorts; } - QuerySolutionNode* clone() const; +protected: + void cloneProjectionData(ProjectionNode* copy) const; + +public: + /** + * Identify projectionImplementation type as a string. + */ + virtual StringData projectionImplementationTypeToString() const = 0; BSONObjSet _sorts; // The full query tree. Needed when we have positional operators. // Owned in the CanonicalQuery, not here. - MatchExpression* fullExpression; + const MatchExpression& fullExpression; // Given that we don't yet have a MatchExpression analogue for the expression language, we // use a BSONObj. BSONObj projection; - // What implementation of the projection algorithm should we use? - ProjectionType projType; - ParsedProjection parsed; +}; + +/** + * This is the most general implementation of the projection functionality. It handles every case. + */ +struct ProjectionNodeDefault final : ProjectionNode { + using ProjectionNode::ProjectionNode; + + StageType getType() const final { + return STAGE_PROJECTION_DEFAULT; + } + + ProjectionNode* clone() const final; + + StringData projectionImplementationTypeToString() const final { + return "DEFAULT"_sd; + } +}; + +/** + * This is a fast-path for when the projection is fully covered by one index. + */ +struct ProjectionNodeCovered final : ProjectionNode { + ProjectionNodeCovered(std::unique_ptr<QuerySolutionNode> child, + const MatchExpression& fullExpression, + BSONObj projection, + ParsedProjection parsed, + BSONObj coveredKeyObj) + : ProjectionNode(std::move(child), fullExpression, projection, parsed), + coveredKeyObj(std::move(coveredKeyObj)) {} + + StageType getType() const final { + return STAGE_PROJECTION_COVERED; + } + + ProjectionNode* clone() const final; + + StringData projectionImplementationTypeToString() const final { + return "COVERED_ONE_INDEX"_sd; + } - // Only meaningful if projType == COVERED_ONE_INDEX. This is the key pattern of the index - // supplying our covered data. We can pre-compute which fields to include and cache that - // data for later if we know we only have one index. + // This is the key pattern of the index supplying our covered data. We can pre-compute which + // fields to include and cache that data for later if we know we only have one index. BSONObj coveredKeyObj; }; +/** + * This is a fast-path for when the projection only has inclusions on non-dotted fields. + */ +struct ProjectionNodeSimple final : ProjectionNode { + using ProjectionNode::ProjectionNode; + + StageType getType() const final { + return STAGE_PROJECTION_SIMPLE; + } + + ProjectionNode* clone() const final; + + StringData projectionImplementationTypeToString() const final { + return "SIMPLE_DOC"_sd; + } +}; + struct SortKeyGeneratorNode : public QuerySolutionNode { StageType getType() const final { return STAGE_SORT_KEY_GENERATOR; diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp index c8555503e7c..c084b1c089c 100644 --- a/src/mongo/db/query/query_solution_test.cpp +++ b/src/mongo/db/query/query_solution_test.cpp @@ -28,6 +28,8 @@ * it in the license file. */ +#include <utility> + #include "mongo/platform/basic.h" #include "mongo/bson/bsontypes.h" @@ -712,8 +714,7 @@ TEST(QuerySolutionTest, IndexScanNodeHasFieldExcludesSimpleBoundsStringFieldWhen ASSERT_FALSE(node.hasField("b")); } -std::unique_ptr<ParsedProjection> createParsedProjection(const BSONObj& query, - const BSONObj& projObj) { +auto createMatchExprAndParsedProjection(const BSONObj& query, const BSONObj& projObj) { QueryTestServiceContext serviceCtx; auto opCtx = serviceCtx.makeOperationContext(); const CollatorInterface* collator = nullptr; @@ -732,7 +733,8 @@ std::unique_ptr<ParsedProjection> createParsedProjection(const BSONObj& query, << status.toString()); } ASSERT(out); - return std::unique_ptr<ParsedProjection>(out); + return std::make_pair(std::move(queryMatchExpr.getValue()), + std::unique_ptr<ParsedProjection>(out)); } TEST(QuerySolutionTest, InclusionProjectionPreservesSort) { @@ -742,10 +744,12 @@ TEST(QuerySolutionTest, InclusionProjectionPreservesSort) { BSONObj projection = BSON("a" << 1); BSONObj match; - auto parsedProjection = createParsedProjection(match, projection); + auto matchExprAndParsedProjection = createMatchExprAndParsedProjection(match, projection); - ProjectionNode proj{*parsedProjection}; - proj.children.push_back(node.release()); + ProjectionNodeDefault proj{std::move(node), + *matchExprAndParsedProjection.first, + projection, + *matchExprAndParsedProjection.second}; proj.computeProperties(); ASSERT_EQ(proj.getSort().size(), 1U); @@ -759,10 +763,12 @@ TEST(QuerySolutionTest, ExclusionProjectionDoesNotPreserveSort) { BSONObj projection = BSON("a" << 0); BSONObj match; - auto parsedProjection = createParsedProjection(match, projection); + auto matchExprAndParsedProjection = createMatchExprAndParsedProjection(match, projection); - ProjectionNode proj{*parsedProjection}; - proj.children.push_back(node.release()); + ProjectionNodeDefault proj{std::move(node), + *matchExprAndParsedProjection.first, + projection, + *matchExprAndParsedProjection.second}; proj.computeProperties(); ASSERT_EQ(proj.getSort().size(), 0U); @@ -774,10 +780,12 @@ TEST(QuerySolutionTest, InclusionProjectionTruncatesSort) { BSONObj projection = BSON("a" << 1); BSONObj match; - auto parsedProjection = createParsedProjection(match, projection); + auto matchExprAndParsedProjection = createMatchExprAndParsedProjection(match, projection); - ProjectionNode proj{*parsedProjection}; - proj.children.push_back(node.release()); + ProjectionNodeDefault proj{std::move(node), + *matchExprAndParsedProjection.first, + projection, + *matchExprAndParsedProjection.second}; proj.computeProperties(); ASSERT_EQ(proj.getSort().size(), 1U); @@ -790,10 +798,12 @@ TEST(QuerySolutionTest, ExclusionProjectionTruncatesSort) { BSONObj projection = BSON("b" << 0); BSONObj match; - auto parsedProjection = createParsedProjection(match, projection); + auto matchExprAndParsedProjection = createMatchExprAndParsedProjection(match, projection); - ProjectionNode proj{*parsedProjection}; - proj.children.push_back(node.release()); + ProjectionNodeDefault proj{std::move(node), + *matchExprAndParsedProjection.first, + projection, + *matchExprAndParsedProjection.second}; proj.computeProperties(); ASSERT_EQ(proj.getSort().size(), 1U); diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index eb3a5354fcb..96c98d71251 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -138,30 +138,38 @@ PlanStage* buildStages(OperationContext* opCtx, return new SortKeyGeneratorStage( opCtx, childStage, ws, keyGenNode->sortSpec, cq.getCollator()); } - case STAGE_PROJECTION: { - const ProjectionNode* pn = static_cast<const ProjectionNode*>(root); + case STAGE_PROJECTION_DEFAULT: { + auto pn = static_cast<const ProjectionNodeDefault*>(root); unique_ptr<PlanStage> childStage{ buildStages(opCtx, collection, cq, qsol, pn->children[0], ws)}; if (nullptr == childStage) { return nullptr; } - - 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 ProjectionStageDefault(opCtx, + pn->projection, + ws, + std::move(childStage), + pn->fullExpression, + cq.getCollator()); + } + case STAGE_PROJECTION_COVERED: { + auto pn = static_cast<const ProjectionNodeCovered*>(root); + unique_ptr<PlanStage> childStage{ + buildStages(opCtx, collection, cq, qsol, pn->children[0], ws)}; + if (nullptr == childStage) { + return nullptr; + } + return new ProjectionStageCovered( + opCtx, pn->projection, ws, std::move(childStage), pn->coveredKeyObj); + } + case STAGE_PROJECTION_SIMPLE: { + auto pn = static_cast<const ProjectionNodeSimple*>(root); + unique_ptr<PlanStage> childStage{ + buildStages(opCtx, collection, cq, qsol, pn->children[0], ws)}; + if (nullptr == childStage) { + return nullptr; } + return new ProjectionStageSimple(opCtx, pn->projection, ws, std::move(childStage)); } case STAGE_LIMIT: { const LimitNode* ln = static_cast<const LimitNode*>(root); diff --git a/src/mongo/db/query/stage_types.h b/src/mongo/db/query/stage_types.h index 6fbdba5ea36..7759aa3bdba 100644 --- a/src/mongo/db/query/stage_types.h +++ b/src/mongo/db/query/stage_types.h @@ -76,7 +76,11 @@ enum StageType { STAGE_MULTI_PLAN, STAGE_OR, - STAGE_PROJECTION, + + // Projection has three alternate implementations. + STAGE_PROJECTION_DEFAULT, + STAGE_PROJECTION_COVERED, + STAGE_PROJECTION_SIMPLE, // Stages for running aggregation pipelines. STAGE_CHANGE_STREAM_PROXY, |