diff options
author | Alyssa Wagenmaker <alyssa.wagenmaker@mongodb.com> | 2022-11-02 18:01:26 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-11-02 19:02:37 +0000 |
commit | 3c525eb2a8593dc7ca59cc13560bc710a8fa89b7 (patch) | |
tree | a4c6e44a504aa2a4412f98e79336095717bec982 | |
parent | 2a6386e6d6c1f847248e468a6407907484f58fb6 (diff) | |
download | mongo-3c525eb2a8593dc7ca59cc13560bc710a8fa89b7.tar.gz |
SERVER-70871 Generate query solution before query engine selection
-rw-r--r-- | src/mongo/db/pipeline/pipeline_d.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 263 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.h | 16 |
3 files changed, 235 insertions, 83 deletions
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp index ac2173ae50c..ec84917b5c5 100644 --- a/src/mongo/db/pipeline/pipeline_d.cpp +++ b/src/mongo/db/pipeline/pipeline_d.cpp @@ -107,7 +107,7 @@ using write_ops::InsertCommandRequest; namespace { /** - * Extracts a prefix of 'DocumentSourceGroup' and 'DocumentSourceLookUp' stages from the given + * Finds a prefix of 'DocumentSourceGroup' and 'DocumentSourceLookUp' stages from the given * pipeline to prepare for pushdown of $group and $lookup into the inner query layer so that it * can be executed using SBE. * Group stages are extracted from the pipeline when all of the following conditions are met: @@ -121,11 +121,11 @@ namespace { * - The $lookup uses only the 'localField'/'foreignField' syntax (no pipelines). * - The foreign collection is neither sharded nor a view. */ -std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleStagesForPushdown( +std::vector<std::unique_ptr<InnerPipelineStageInterface>> findSbeCompatibleStagesForPushdown( const intrusive_ptr<ExpressionContext>& expCtx, const MultipleCollectionAccessor& collections, const CanonicalQuery* cq, - Pipeline* pipeline) { + const Pipeline* pipeline) { // We will eventually use the extracted group stages to populate 'CanonicalQuery::pipeline' // which requires stages to be wrapped in an interface. std::vector<std::unique_ptr<InnerPipelineStageInterface>> stagesForPushdown; @@ -140,7 +140,7 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt return {}; } - auto&& sources = pipeline->getSources(); + const auto& sources = pipeline->getSources(); bool isMainCollectionSharded = false; if (const auto& mainColl = collections.getMainCollection()) { @@ -158,7 +158,7 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt internalQuerySlotBasedExecutionDisableLookupPushdown.load() || isMainCollectionSharded || collections.isAnySecondaryNamespaceAViewOrSharded(); - for (auto itr = sources.begin(); itr != sources.end();) { + for (auto itr = sources.begin(); itr != sources.end(); ++itr) { const bool isLastSource = itr->get() == sources.back().get(); // $group pushdown logic. @@ -170,7 +170,6 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt if (groupStage->sbeCompatible() && !groupStage->doingMerge()) { stagesForPushdown.push_back( std::make_unique<InnerPipelineStageImpl>(groupStage, isLastSource)); - sources.erase(itr++); continue; } break; @@ -187,7 +186,6 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt if (lookupStage->sbeCompatible()) { stagesForPushdown.push_back( std::make_unique<InnerPipelineStageImpl>(lookupStage, isLastSource)); - sources.erase(itr++); continue; } break; @@ -199,6 +197,21 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt return stagesForPushdown; } +/** + * Removes the first 'stagesToRemove' stages from the pipeline. This function is meant to be paired + * with a call to findSbeCompatibleStagesForPushdown() - the caller must first get the stages to + * push down, then remove them. + */ +void trimPipelineStages(Pipeline* pipeline, size_t stagesToRemove) { + auto& sources = pipeline->getSources(); + tassert(7087104, + "stagesToRemove must be <= number of pipeline sources", + stagesToRemove <= sources.size()); + for (size_t i = 0; i < stagesToRemove; ++i) { + sources.erase(sources.begin()); + } +} + StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExecutor( const intrusive_ptr<ExpressionContext>& expCtx, const MultipleCollectionAccessor& collections, @@ -296,9 +309,15 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe return getExecutorFind(expCtx->opCtx, collections, std::move(cq.getValue()), - [&](auto* canonicalQuery) { - canonicalQuery->setPipeline(extractSbeCompatibleStagesForPushdown( - expCtx, collections, canonicalQuery, pipeline)); + [&](auto* canonicalQuery, bool attachOnly) { + if (attachOnly) { + canonicalQuery->setPipeline(findSbeCompatibleStagesForPushdown( + expCtx, collections, canonicalQuery, pipeline)); + } else { + // Not attaching - we need to trim the already pushed down + // pipeline stages from the pipeline. + trimPipelineStages(pipeline, canonicalQuery->pipeline().size()); + } }, permitYield, plannerOpts); diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index c93ad5a1e42..9e5d55a9d97 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -530,7 +530,9 @@ private: * - A vector of PlanStages, representing the roots of the constructed execution trees (in the * case when the query has multiple solutions, we may construct an execution tree for each * solution and pick the best plan after multi-planning). Elements of this vector can never be - * null. The size of this vector must always match the size of 'querySolutions' vector. + * null. The size of this vector must always be empty or match the size of 'querySolutions' + * vector. It will be empty in circumstances where we only construct query solutions and delay + * building execution trees, which is any time we are not using a cached plan. * - A root node of the extension plan. The plan can be combined with a solution to create a * larger plan after the winning solution is found. Can be null, meaning "no extension". * - An optional decisionWorks value, which is populated when a solution was reconstructed from @@ -546,9 +548,11 @@ public: using PlanStageVector = std::vector<std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData>>; - void emplace(std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root, - std::unique_ptr<QuerySolution> solution) { - _roots.push_back(std::move(root)); + void emplace(std::unique_ptr<QuerySolution> solution) { + // Only allow solutions to be added, execution trees will be generated later. + tassert(7087100, + "expected execution trees to be generated after query solutions", + _roots.empty()); _solutions.push_back(std::move(solution)); } @@ -560,11 +564,11 @@ public: std::string getPlanSummary() const { // We can report plan summary only if this result contains a single solution. - invariant(_roots.size() == 1); - invariant(_solutions.size() == 1); - invariant(_roots[0].first); + tassert(7087101, "expected exactly one solution", _solutions.size() == 1); + tassert(7087102, "expected at most one execution tree", _roots.size() <= 1); + // We only need the query solution to build the explain summary. auto explainer = plan_explainer_factory::make( - _roots[0].first.get(), &_roots[0].second, _solutions[0].get()); + nullptr /* root */, nullptr /* data */, _solutions[0].get()); return explainer->getPlanSummary(); } @@ -572,6 +576,14 @@ public: return std::make_pair(std::move(_roots), std::move(_solutions)); } + const QuerySolutionVector& solutions() const { + return _solutions; + } + + const PlanStageVector& roots() const { + return _roots; + } + boost::optional<size_t> decisionWorks() const { return _decisionWorks; } @@ -621,8 +633,14 @@ private: * normal stage builder process. * * We have a QuerySolutionNode tree (or multiple query solution trees), but must execute some * custom logic in order to build the final execution tree. + * + * In most cases, the helper bypasses the final step of building the execution tree and returns only + * the query solution(s) if the 'DeferExecutionTreeGeneration' flag is true. */ -template <typename KeyType, typename PlanStageType, typename ResultType> +template <typename KeyType, + typename PlanStageType, + typename ResultType, + bool DeferExecutionTreeGeneration> class PrepareExecutionHelper { public: PrepareExecutionHelper(OperationContext* opCtx, @@ -650,11 +668,8 @@ public: auto solution = std::make_unique<QuerySolution>(); solution->setRoot(std::make_unique<EofNode>()); - - auto root = buildExecutableTree(*solution); - auto result = makeResult(); - result->emplace(std::move(root), std::move(solution)); + addSolutionToResult(result.get(), std::move(solution)); return std::move(result); } @@ -679,12 +694,6 @@ public: } auto planCacheKey = buildPlanCacheKey(); - // Fill in some opDebug information, unless it has already been filled by an outer pipeline. - OpDebug& opDebug = CurOp::get(_opCtx)->debug(); - if (!opDebug.queryHash) { - opDebug.queryHash = planCacheKey.queryHash(); - } - if (auto result = buildCachedPlan(planCacheKey)) { return {std::move(result)}; } @@ -715,8 +724,7 @@ public: for (size_t i = 0; i < solutions.size(); ++i) { if (turnIxscanIntoCount(solutions[i].get())) { auto result = makeResult(); - auto root = buildExecutableTree(*solutions[i]); - result->emplace(std::move(root), std::move(solutions[i])); + addSolutionToResult(result.get(), std::move(solutions[i])); LOGV2_DEBUG(20925, 2, @@ -731,9 +739,8 @@ public: if (1 == solutions.size()) { // Only one possible plan. Build the stages from the solution. auto result = makeResult(); - auto root = buildExecutableTree(*solutions[0]); solutions[0]->indexFilterApplied = _plannerParams.indexFiltersApplied; - result->emplace(std::move(root), std::move(solutions[0])); + addSolutionToResult(result.get(), std::move(solutions[0])); LOGV2_DEBUG(20926, 2, @@ -748,6 +755,8 @@ public: } protected: + static constexpr bool ShouldDeferExecutionTreeGeneration = DeferExecutionTreeGeneration; + /** * Creates a result instance to be returned to the caller holding the result of the * prepare() call. @@ -757,6 +766,19 @@ protected: } /** + * Adds the query solution to the result object, additionally building the corresponding + * execution tree if 'DeferExecutionTreeGeneration' is turned on. + */ + void addSolutionToResult(ResultType* result, std::unique_ptr<QuerySolution> solution) { + if constexpr (!DeferExecutionTreeGeneration) { + auto root = buildExecutableTree(*solution); + result->emplace(std::move(root), std::move(solution)); + } else { + result->emplace(std::move(solution)); + } + } + + /** * Fills out planner parameters if not already filled. */ void initializePlannerParamsIfNeeded() { @@ -820,7 +842,8 @@ protected: class ClassicPrepareExecutionHelper final : public PrepareExecutionHelper<PlanCacheKey, std::unique_ptr<PlanStage>, - ClassicPrepareExecutionResult> { + ClassicPrepareExecutionResult, + false /* DeferExecutionTreeGeneration */> { public: ClassicPrepareExecutionHelper(OperationContext* opCtx, const CollectionPtr& collection, @@ -923,6 +946,12 @@ protected: const PlanCacheKey& planCacheKey) final { initializePlannerParamsIfNeeded(); + // Fill in some opDebug information, unless it has already been filled by an outer pipeline. + OpDebug& opDebug = CurOp::get(_opCtx)->debug(); + if (!opDebug.queryHash) { + opDebug.queryHash = planCacheKey.queryHash(); + } + // Before consulting the plan cache, check if we should short-circuit and construct a // find-by-_id plan. std::unique_ptr<ClassicPrepareExecutionResult> result = buildIdHackPlan(); @@ -1017,7 +1046,8 @@ class SlotBasedPrepareExecutionHelper final : public PrepareExecutionHelper< sbe::PlanCacheKey, std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData>, - SlotBasedPrepareExecutionResult> { + SlotBasedPrepareExecutionResult, + true /* DeferExecutionTreeGeneration */> { public: using PrepareExecutionHelper::PrepareExecutionHelper; @@ -1038,6 +1068,9 @@ public: std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> buildExecutableTree( const QuerySolution& solution) const final { + tassert(7087105, + "template indicates execution tree generation deferred", + !ShouldDeferExecutionTreeGeneration); return stage_builder::buildSlotBasedExecutableTree( _opCtx, _collections, *_cq, solution, _yieldPolicy); } @@ -1053,11 +1086,6 @@ protected: if (!feature_flags::gFeatureFlagSbeFull.isEnabledAndIgnoreFCV()) { return buildCachedPlanFromClassicCache(); } else { - OpDebug& opDebug = CurOp::get(_opCtx)->debug(); - if (!opDebug.planCacheKey) { - opDebug.planCacheKey = planCacheKey.planCacheKeyHash(); - } - auto&& planCache = sbe::getPlanCache(_opCtx); auto cacheEntry = planCache.getCacheEntryIfActive(planCacheKey); if (!cacheEntry) { @@ -1089,10 +1117,6 @@ protected: std::unique_ptr<SlotBasedPrepareExecutionResult> buildCachedPlanFromClassicCache() { const auto& mainColl = getMainCollection(); auto planCacheKey = plan_cache_key_factory::make<PlanCacheKey>(*_cq, mainColl); - OpDebug& opDebug = CurOp::get(_opCtx)->debug(); - if (!opDebug.planCacheKey) { - opDebug.planCacheKey = planCacheKey.planCacheKeyHash(); - } // Try to look up a cached solution for the query. if (auto cs = CollectionQueryInfo::get(mainColl).getPlanCache()->getCacheEntryIfActive( planCacheKey)) { @@ -1109,9 +1133,7 @@ protected: } auto result = makeResult(); - auto&& execTree = buildExecutableTree(*querySolution); - - result->emplace(std::move(execTree), std::move(querySolution)); + addSolutionToResult(result.get(), std::move(querySolution)); result->setDecisionWorks(cs->decisionWorks); result->setRecoveredFromPlanCache(true); return result; @@ -1134,8 +1156,7 @@ protected: for (size_t ix = 0; ix < solutions.size(); ++ix) { solutions[ix]->indexFilterApplied = _plannerParams.indexFiltersApplied; - auto execTree = buildExecutableTree(*solutions[ix]); - result->emplace(std::move(execTree), std::move(solutions[ix])); + addSolutionToResult(result.get(), std::move(solutions[ix])); } return result; } @@ -1256,29 +1277,40 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe OperationContext* opCtx, const MultipleCollectionAccessor& collections, std::unique_ptr<CanonicalQuery> cq, - PlanYieldPolicy::YieldPolicy requestedYieldPolicy, - const QueryPlannerParams& plannerParams) { - // Mark that this query uses the SBE engine, unless this has already been set. + std::unique_ptr<PlanYieldPolicySBE> yieldPolicy, + const QueryPlannerParams& plannerParams, + std::unique_ptr<SlotBasedPrepareExecutionResult> planningResult) { + // Now that we know what executor we are going to use, fill in some opDebug information, unless + // it has already been filled by an outer pipeline. OpDebug& opDebug = CurOp::get(opCtx)->debug(); if (!opDebug.classicEngineUsed) { opDebug.classicEngineUsed = false; } - - const auto mainColl = &collections.getMainCollection(); + if (collections.getMainCollection()) { + auto planCacheKey = plan_cache_key_factory::make(*cq, collections); + if (!opDebug.queryHash) { + opDebug.queryHash = planCacheKey.queryHash(); + } + if (!opDebug.planCacheKey && shouldCacheQuery(*cq)) { + opDebug.planCacheKey = planCacheKey.planCacheKeyHash(); + } + } // Analyze the provided query and build the list of candidate plans for it. auto nss = cq->nss(); - auto yieldPolicy = makeSbeYieldPolicy(opCtx, requestedYieldPolicy, mainColl, nss); - SlotBasedPrepareExecutionHelper helper{ - opCtx, collections, cq.get(), yieldPolicy.get(), plannerParams.options}; - auto planningResultWithStatus = helper.prepare(); - if (!planningResultWithStatus.isOK()) { - return planningResultWithStatus.getStatus(); - } - auto&& planningResult = planningResultWithStatus.getValue(); auto&& [roots, solutions] = planningResult->extractResultData(); + invariant(roots.empty() || roots.size() == solutions.size()); + if (roots.empty()) { + // We might have execution trees already if we pulled the plan from the cache. If not, we + // need to generate one for each solution. + for (const auto& solution : solutions) { + roots.emplace_back(stage_builder::buildSlotBasedExecutableTree( + opCtx, collections, *cq, *solution, yieldPolicy.get())); + } + } + // When query requires sub-planning, we may not get any executable plans. const auto planStageData = roots.empty() ? boost::none @@ -1318,7 +1350,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe *cq, std::move(solutions[0]), fillOutSecondaryCollectionsInformation(opCtx, collections, cq.get())); - roots[0] = helper.buildExecutableTree(*(solutions[0])); + roots[0] = stage_builder::buildSlotBasedExecutableTree( + opCtx, collections, *cq, *(solutions[0]), yieldPolicy.get()); } plan_cache_util::updatePlanCache(opCtx, collections, *cq, *solutions[0], *root, data); @@ -1338,13 +1371,99 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe std::move(yieldPolicy), planningResult->isRecoveredFromPlanCache()); } + +/** + * Checks if the result of query planning is SBE compatible. + */ +bool isPlanSbeCompatible(const SlotBasedPrepareExecutionResult& planningResult) { + const auto& solutions = planningResult.solutions(); + if (solutions.empty()) { + // Query needs subplanning (plans are generated later, we don't have access yet). + invariant(planningResult.needsSubplanning()); + } + + // Check that the query solution is SBE compatible. + + return true; +} + +/** + * Attempts to create a slot-based executor for the query, if the query plan is eligible for SBE + * execution. This function has three possible return values: + * + * 1. A plan executor. This is in the case where the query is SBE eligible and encounters no errors + * in executor creation. This result is to be expected in the majority of cases. + * 2. A non-OK status. This is when errors are encountered during executor creation. + * 3. The canonical query. This is to return ownership of the 'canonicalQuery' argument in the case + * where the query plan is not eligible for SBE execution but it is not an error case. + */ +StatusWith<stdx::variant<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>, + std::unique_ptr<CanonicalQuery>>> +attemptToGetSlotBasedExecutor( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + std::unique_ptr<CanonicalQuery> canonicalQuery, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, + PlanYieldPolicy::YieldPolicy yieldPolicy, + const QueryPlannerParams& plannerParams) { + if (extractAndAttachPipelineStages) { + // Push down applicable pipeline stages and attach to the query, but don't remove from + // the high-level pipeline object until we know for sure we will execute with SBE. + extractAndAttachPipelineStages(canonicalQuery.get(), true /* attachOnly */); + } + + // Use SBE if we find any $group/$lookup stages eligible for execution in SBE or if SBE + // is fully enabled. Otherwise, fallback to the classic engine. + if (canonicalQuery->pipeline().empty() && + !feature_flags::gFeatureFlagSbeFull.isEnabledAndIgnoreFCV()) { + canonicalQuery->setSbeCompatible(false); + } else { + // Construct the SBE query solution - this will be our final decision stage to determine + // whether SBE should be used. + auto sbeYieldPolicy = makeSbeYieldPolicy( + opCtx, yieldPolicy, &collections.getMainCollection(), canonicalQuery->nss()); + + SlotBasedPrepareExecutionHelper helper{ + opCtx, collections, canonicalQuery.get(), sbeYieldPolicy.get(), plannerParams.options}; + auto planningResultWithStatus = helper.prepare(); + if (planningResultWithStatus.isOK() && + isPlanSbeCompatible(*planningResultWithStatus.getValue())) { + if (extractAndAttachPipelineStages) { + // We know now that we will use SBE, so we need to remove the pushed-down stages + // from the original pipeline object. + extractAndAttachPipelineStages(canonicalQuery.get(), false /* attachOnly */); + } + auto statusWithExecutor = + getSlotBasedExecutor(opCtx, + collections, + std::move(canonicalQuery), + std::move(sbeYieldPolicy), + plannerParams, + std::move(planningResultWithStatus.getValue())); + if (statusWithExecutor.isOK()) { + return std::move(statusWithExecutor.getValue()); + } else { + return statusWithExecutor.getStatus(); + } + } + + // Query plan was not SBE compatible - reset any fields that may have been modified, and + // fall back to classic engine. + canonicalQuery->setSbeCompatible(false); + canonicalQuery->setPipeline({}); + } + + // Return ownership of the canonical query to the caller. + return std::move(canonicalQuery); +} + } // namespace StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( OperationContext* opCtx, const MultipleCollectionAccessor& collections, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, PlanYieldPolicy::YieldPolicy yieldPolicy, const QueryPlannerParams& plannerParams) { invariant(canonicalQuery); @@ -1358,17 +1477,27 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( // Use SBE if 'canonicalQuery' is SBE compatible. if (!canonicalQuery->getForceClassicEngine() && canonicalQuery->isSbeCompatible()) { - if (extractAndAttachPipelineStages) { - extractAndAttachPipelineStages(canonicalQuery.get()); - } - // Use SBE if we find any $group/$lookup stages eligible for execution in SBE or if SBE - // is fully enabled. Otherwise, fallback to the classic engine. - if (canonicalQuery->pipeline().empty() && - !feature_flags::gFeatureFlagSbeFull.isEnabledAndIgnoreFCV()) { - canonicalQuery->setSbeCompatible(false); + auto statusWithExecutor = attemptToGetSlotBasedExecutor(opCtx, + collections, + std::move(canonicalQuery), + extractAndAttachPipelineStages, + yieldPolicy, + plannerParams); + if (!statusWithExecutor.isOK()) { + return statusWithExecutor.getStatus(); + } + auto& maybeExecutor = statusWithExecutor.getValue(); + if (stdx::holds_alternative<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>>( + maybeExecutor)) { + return std::move( + stdx::get<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>>(maybeExecutor)); } else { - return getSlotBasedExecutor( - opCtx, collections, std::move(canonicalQuery), yieldPolicy, plannerParams); + // The query is not eligible for SBE execution - reclaim the canonical query and fall + // back to classic. + tassert(7087103, + "return value must contain canonical query if not executor", + stdx::holds_alternative<std::unique_ptr<CanonicalQuery>>(maybeExecutor)); + canonicalQuery = std::move(stdx::get<std::unique_ptr<CanonicalQuery>>(maybeExecutor)); } } @@ -1380,7 +1509,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( OperationContext* opCtx, const CollectionPtr* collection, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, PlanYieldPolicy::YieldPolicy yieldPolicy, size_t plannerOptions) { MultipleCollectionAccessor multi{collection}; @@ -1400,7 +1529,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind OperationContext* opCtx, const MultipleCollectionAccessor& collections, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, bool permitYield, QueryPlannerParams plannerParams) { auto yieldPolicy = (permitYield && !opCtx->inMultiDocumentTransaction()) @@ -1423,7 +1552,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind OperationContext* opCtx, const CollectionPtr* coll, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, bool permitYield, size_t plannerOptions) { MultipleCollectionAccessor multi{*coll}; diff --git a/src/mongo/db/query/get_executor.h b/src/mongo/db/query/get_executor.h index 75ab5475f2f..a1e24952b76 100644 --- a/src/mongo/db/query/get_executor.h +++ b/src/mongo/db/query/get_executor.h @@ -155,7 +155,9 @@ bool shouldWaitForOplogVisibility(OperationContext* opCtx, * If the caller provides a 'extractAndAttachPipelineStages' function and the query is eligible for * pushdown into the find layer this function will be invoked to extract pipeline stages and * attach them to the provided 'CanonicalQuery'. This function should capture the Pipeline that - * stages should be extracted from. + * stages should be extracted from. If the boolean 'attachOnly' argument is true, it will only find + * and attach the applicable stages to the query. If it is false, it will remove the extracted + * stages from the pipeline. * * Note that the first overload takes a 'MultipleCollectionAccessor' and can construct a * PlanExecutor over multiple collections, while the second overload takes a single 'CollectionPtr' @@ -165,7 +167,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( OperationContext* opCtx, const MultipleCollectionAccessor& collections, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, PlanYieldPolicy::YieldPolicy yieldPolicy, const QueryPlannerParams& plannerOptions); @@ -173,7 +175,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( OperationContext* opCtx, const CollectionPtr* collection, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, PlanYieldPolicy::YieldPolicy yieldPolicy, size_t plannerOptions = 0); @@ -190,7 +192,9 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( * If the caller provides a 'extractAndAttachPipelineStages' function and the query is eligible for * pushdown into the find layer this function will be invoked to extract pipeline stages and * attach them to the provided 'CanonicalQuery'. This function should capture the Pipeline that - * stages should be extracted from. + * stages should be extracted from. If the boolean 'attachOnly' argument is true, it will only find + * and attach the applicable stages to the query. If it is false, it will remove the extracted + * stages from the pipeline. * * Note that the first overload takes a 'MultipleCollectionAccessor' and can construct a * PlanExecutor over multiple collections, while the second overload takes a single @@ -200,7 +204,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind OperationContext* opCtx, const MultipleCollectionAccessor& collections, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, bool permitYield = false, QueryPlannerParams plannerOptions = QueryPlannerParams{}); @@ -208,7 +212,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind OperationContext* opCtx, const CollectionPtr* collection, std::unique_ptr<CanonicalQuery> canonicalQuery, - std::function<void(CanonicalQuery*)> extractAndAttachPipelineStages, + std::function<void(CanonicalQuery*, bool)> extractAndAttachPipelineStages, bool permitYield = false, size_t plannerOptions = QueryPlannerParams::DEFAULT); |