From 7a1cf68baa67b8b6d4e138d6ea5d2d7c2bca0dfe Mon Sep 17 00:00:00 2001 From: Matthew Boros Date: Wed, 4 Feb 2026 00:33:10 -0500 Subject: [PATCH] SERVER-117423 Factor out common get_executor logic into new files (#47189) GitOrigin-RevId: cb3802eb34492a3342080ed6842c3fbfeb54752a --- .github/CODEOWNERS | 1 + src/mongo/db/BUILD.bazel | 3 + src/mongo/db/query/OWNERS.yml | 3 + src/mongo/db/query/engine_selection.cpp | 226 +++++++++++ src/mongo/db/query/engine_selection.h | 58 +++ src/mongo/db/query/get_executor.cpp | 360 ++---------------- .../db/query/get_executor_fast_paths.cpp | 182 +++++++++ src/mongo/db/query/get_executor_fast_paths.h | 92 +++++ src/mongo/db/query/get_executor_helpers.cpp | 137 +++++++ src/mongo/db/query/get_executor_helpers.h | 92 +++++ src/mongo/db/query/query_planner_params.cpp | 3 +- src/mongo/db/query/query_planner_params.h | 3 +- 12 files changed, 830 insertions(+), 330 deletions(-) create mode 100644 src/mongo/db/query/engine_selection.cpp create mode 100644 src/mongo/db/query/engine_selection.h create mode 100644 src/mongo/db/query/get_executor_fast_paths.cpp create mode 100644 src/mongo/db/query/get_executor_fast_paths.h create mode 100644 src/mongo/db/query/get_executor_helpers.cpp create mode 100644 src/mongo/db/query/get_executor_helpers.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 776a2431487..31797f0f034 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2591,6 +2591,7 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot /src/mongo/db/query/**/count_request* @10gen/query-optimization @svc-auto-approve-bot /src/mongo/db/query/**/dbref.h @10gen/query-optimization @svc-auto-approve-bot /src/mongo/db/query/**/distinct_access* @10gen/query-optimization @svc-auto-approve-bot +/src/mongo/db/query/**/engine_selection* @10gen/query-execution @svc-auto-approve-bot /src/mongo/db/query/**/eof_node_type* @10gen/query-optimization @svc-auto-approve-bot /src/mongo/db/query/**/explain* @10gen/query-optimization @svc-auto-approve-bot /src/mongo/db/query/**/expression_walker* @10gen/query-optimization @svc-auto-approve-bot diff --git a/src/mongo/db/BUILD.bazel b/src/mongo/db/BUILD.bazel index bb43ba19922..b8ad25d6030 100644 --- a/src/mongo/db/BUILD.bazel +++ b/src/mongo/db/BUILD.bazel @@ -871,9 +871,12 @@ mongo_cc_library( "//src/mongo/db/pipeline:plan_explainer_pipeline.cpp", "//src/mongo/db/pipeline:sbe_pushdown.cpp", "//src/mongo/db/query:all_indices_required_checker.cpp", + "//src/mongo/db/query:engine_selection.cpp", "//src/mongo/db/query:explain.cpp", "//src/mongo/db/query:find.cpp", "//src/mongo/db/query:get_executor.cpp", + "//src/mongo/db/query:get_executor_fast_paths.cpp", + "//src/mongo/db/query:get_executor_helpers.cpp", "//src/mongo/db/query:internal_plans.cpp", "//src/mongo/db/query:plan_executor_factory.cpp", "//src/mongo/db/query:plan_executor_impl.cpp", diff --git a/src/mongo/db/query/OWNERS.yml b/src/mongo/db/query/OWNERS.yml index b1eabdfc6e9..73585a38335 100644 --- a/src/mongo/db/query/OWNERS.yml +++ b/src/mongo/db/query/OWNERS.yml @@ -49,6 +49,9 @@ filters: - "distinct_access*": approvers: - 10gen/query-optimization + - "engine_selection*": + approvers: + - 10gen/query-execution - "eof_node_type*": approvers: - 10gen/query-optimization diff --git a/src/mongo/db/query/engine_selection.cpp b/src/mongo/db/query/engine_selection.cpp new file mode 100644 index 00000000000..29201aa2ee6 --- /dev/null +++ b/src/mongo/db/query/engine_selection.cpp @@ -0,0 +1,226 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/query/engine_selection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +// IWYU pragma: no_include "ext/alloc_traits.h" +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/pipeline/document_source_lookup.h" +#include "mongo/db/pipeline/sbe_pushdown.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/plan_cache/plan_cache.h" +#include "mongo/db/query/query_utils.h" + + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery + +namespace mongo { +namespace { +/** + * Returns true iff 'descriptor' has fields A and B where all of the following hold + * + * - A is a path prefix of B + * - A is a hashed field in the index + * - B is a non-hashed field in the index + * + * TODO SERVER-99889 this is a workaround for an SBE stage builder bug. + */ +bool indexHasHashedPathPrefixOfNonHashedPath(const IndexDescriptor* descriptor) { + boost::optional hashedPath; + for (const auto& elt : descriptor->keyPattern()) { + if (elt.valueStringDataSafe() == "hashed") { + // Indexes may only contain one hashed field. + hashedPath = elt.fieldNameStringData(); + break; + } + } + if (hashedPath == boost::none) { + // No hashed fields in the index. + return false; + } + // Check if 'hashedPath' is a path prefix for any field in the index. + for (const auto& elt : descriptor->keyPattern()) { + if (expression::isPathPrefixOf(hashedPath.get(), elt.fieldNameStringData())) { + return true; + } + } + return false; +} + +/** + * Returns true if 'collection' has an index that contains two fields, one of which is a path prefix + * of the other, where the prefix field is hashed. Indexes can only contain one hashed field. + * + * TODO SERVER-99889: At the time of writing, there is a bug in the SBE stage builders that + * constructs ExpressionFieldPaths over hashed values. This leads to wrong query results. + * + * The bug arises for covered index scans where a path P is a non-hashed path in the index and a + * strict prefix P' of P is a hashed path in the index. + */ +bool collectionHasIndexWithHashedPathPrefixOfNonHashedPath(const CollectionPtr& collection, + ExpressionContext* expCtx) { + const IndexCatalog* indexCatalog = collection->getIndexCatalog(); + tassert(10230200, "'CollectionPtr' does not have an 'IndexCatalog'", indexCatalog); + OperationContext* opCtx = expCtx->getOperationContext(); + tassert(10230201, "'ExpressionContext' does not have an 'OperationContext'", opCtx); + std::unique_ptr indexIter = + indexCatalog->getIndexIterator(IndexCatalog::InclusionPolicy::kReady); + while (indexIter->more()) { + const IndexCatalogEntry* entry = indexIter->next(); + if (indexHasHashedPathPrefixOfNonHashedPath(entry->descriptor())) { + return true; + } + } + return false; +} + +/** + * Checks if the given query can be executed with the SBE engine based on the canonical query. + * + * This method determines whether the query may be compatible with SBE based only on high-level + * information from the canonical query, before query planning has taken place (such as ineligible + * expressions or collections). + * + * If this method returns true, query planning should be done, followed by another layer of + * validation to make sure the query plan can be executed with SBE. If it returns false, SBE query + * planning can be short-circuited as it is already known that the query is ineligible for SBE. + */ +bool isQuerySbeCompatible(const CollectionPtr& collection, const CanonicalQuery& cq) { + auto expCtx = cq.getExpCtxRaw(); + + // If we don't support all expressions used or the query is eligible for IDHack, don't use SBE. + if (!expCtx || expCtx->getSbeCompatibility() == SbeCompatibility::notCompatible || + expCtx->getSbePipelineCompatibility() == SbeCompatibility::notCompatible || + (collection && isIdHackEligibleQuery(collection, cq))) { + return false; + } + + const auto* proj = cq.getProj(); + if (proj && (proj->requiresMatchDetails() || proj->containsElemMatch())) { + return false; + } + + // Tailable and resumed scans are not supported either. + if (expCtx->isTailable() || cq.getFindCommandRequest().getRequestResumeToken()) { + return false; + } + + const auto& nss = cq.nss(); + + const auto isTimeseriesColl = collection && collection->isTimeseriesCollection(); + + auto& queryKnob = cq.getExpCtx()->getQueryKnobConfiguration(); + if ((!feature_flags::gFeatureFlagTimeSeriesInSbe.isEnabled() || + queryKnob.getSbeDisableTimeSeriesForOp()) && + isTimeseriesColl) { + return false; + } + + // Queries against the oplog are not supported. Also queries on the inner side of a $lookup are + // not considered for SBE except search queries. + if ((expCtx->getInLookup() && !cq.isSearchQuery()) || nss.isOplog() || + !cq.metadataDeps().none()) { + return false; + } + + + // Queries against collections with a particular shape of compound hashed indexes are not + // supported. + if (collection && collectionHasIndexWithHashedPathPrefixOfNonHashedPath(collection, expCtx)) { + return false; + } + + // Find and aggregate queries with the $_startAt parameter are not supported in SBE. + if (!cq.getFindCommandRequest().getStartAt().isEmpty()) { + return false; + } + + const auto& sortPattern = cq.getSortPattern(); + return !sortPattern || isSortSbeCompatible(*sortPattern); +} + +/** + * Function which returns true if 'cq' uses features that are currently supported in SBE without + * 'featureFlagSbeFull' being set; false otherwise. + */ +bool shouldUseRegularSbe(OperationContext* opCtx, + const CanonicalQuery& cq, + const CollectionPtr& mainCollection, + const bool sbeFull) { + // When featureFlagSbeFull is not enabled, we cannot use SBE unless 'trySbeEngine' is enabled or + // if 'trySbeRestricted' is enabled, and we have eligible pushed down stages in the cq pipeline. + auto& queryKnob = cq.getExpCtx()->getQueryKnobConfiguration(); + if (!queryKnob.canPushDownFullyCompatibleStages() && cq.cqPipeline().empty()) { + return false; + } + + if (mainCollection && mainCollection->isTimeseriesCollection() && cq.cqPipeline().empty()) { + // TS queries only use SBE when there's a pipeline. + return false; + } + + // Return true if all the expressions in the CanonicalQuery's filter and projection are SBE + // compatible. + SbeCompatibility minRequiredCompatibility = + getMinRequiredSbeCompatibility(queryKnob.getInternalQueryFrameworkControlForOp(), sbeFull); + return cq.getExpCtx()->getSbeCompatibility() >= minRequiredCompatibility; +} +} // namespace + +bool useSbe(OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + CanonicalQuery* cq, + Pipeline* pipeline, + bool needsMerge, + std::unique_ptr plannerParams) { + const auto& mainColl = collections.getMainCollection(); + const bool forceClassic = + cq->getExpCtx()->getQueryKnobConfiguration().isForceClassicEngineEnabled(); + if (forceClassic || !isQuerySbeCompatible(mainColl, *cq)) { + return false; + } + + // Add the stages that are candidates for SBE lowering from the 'pipeline' into the + // 'canonicalQuery'. This must be done _before_ checking shouldUseRegularSbe() or + // creating the planner. + attachPipelineStages(collections, pipeline, needsMerge, cq, std::move(plannerParams)); + + const bool sbeFull = feature_flags::gFeatureFlagSbeFull.isEnabled(); + return sbeFull || shouldUseRegularSbe(opCtx, *cq, mainColl, sbeFull); +} + +} // namespace mongo diff --git a/src/mongo/db/query/engine_selection.h b/src/mongo/db/query/engine_selection.h new file mode 100644 index 00000000000..e6f003125cb --- /dev/null +++ b/src/mongo/db/query/engine_selection.h @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/compiler/physical_model/query_solution/query_solution.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/db/query/write_ops/canonical_update.h" +#include "mongo/db/query/write_ops/parsed_delete.h" +#include "mongo/db/shard_role/shard_catalog/index_catalog_entry.h" +#include "mongo/util/modules.h" + +#include +#include +#include + +namespace mongo { + +/* + * Returns true if SBE should be used given the query details. An optional query solution may be + * passed in, which will be analyzed for SBE eligibility depending on the plan shape. + */ +bool useSbe(OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + CanonicalQuery* cq, + Pipeline* pipeline, + bool needsMerge, + std::unique_ptr plannerParams); + +} // namespace mongo diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index ff90baa42de..2ab5a7c25d5 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -75,7 +75,10 @@ #include "mongo/db/query/compiler/parsers/matcher/expression_parser.h" #include "mongo/db/query/compiler/physical_model/query_solution/eof_node_type.h" #include "mongo/db/query/distinct_access.h" +#include "mongo/db/query/engine_selection.h" #include "mongo/db/query/find_command.h" +#include "mongo/db/query/get_executor_fast_paths.h" +#include "mongo/db/query/get_executor_helpers.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/query/plan_cache/classic_plan_cache.h" #include "mongo/db/query/plan_cache/plan_cache.h" @@ -147,28 +150,6 @@ boost::intrusive_ptr makeExpressionContextForGetExecutor( } namespace { -/** - * Struct to hold information about a query plan's cache info. - */ -struct PlanCacheInfo { - boost::optional planCacheKey; - boost::optional planCacheShapeHash; -}; - -/** - * Fills in the given information on the CurOp::OpDebug object, if it has not already been filled in - * by an outer pipeline. - */ -void setOpDebugPlanCacheInfo(OperationContext* opCtx, const PlanCacheInfo& cacheInfo) { - OpDebug& opDebug = CurOp::get(opCtx)->debug(); - if (!opDebug.planCacheShapeHash && cacheInfo.planCacheShapeHash) { - opDebug.planCacheShapeHash = *cacheInfo.planCacheShapeHash; - } - if (!opDebug.planCacheKey && cacheInfo.planCacheKey) { - opDebug.planCacheKey = *cacheInfo.planCacheKey; - } -} - /** * A class to hold the result of preparation of the query to be executed using SBE engine. This * result stores and provides the following information: @@ -771,25 +752,13 @@ private: } std::unique_ptr buildIdHackPlan() final { - const auto& mainCollection = getCollections().getMainCollection(); - if (!isIdHackEligibleQuery(mainCollection, *_cq)) { + auto idHackPlanner = + tryIdHack(_opCtx, getCollections(), _cq, [this]() { return makePlannerData(); }); + if (!idHackPlanner) { return nullptr; } - - const auto indexEntry = mainCollection->getIndexCatalog()->findIdIndex(_opCtx); - if (!indexEntry) { - return nullptr; - } - - LOGV2_DEBUG(20922, - 2, - "Using classic engine idhack", - "canonicalQuery"_attr = redact(_queryStringForDebugLog)); - planCacheCounters.incrementClassicSkippedCounter(); - fastPathQueryCounters.incrementIdHackQueryCounter(); auto result = releaseResult(); - result->runtimePlanner = - std::make_unique(makePlannerData(), indexEntry); + result->runtimePlanner = std::move(idHackPlanner); return result; } @@ -1187,33 +1156,6 @@ std::unique_ptr getClassicPlannerForSbe( return std::move(planningResult->runtimePlanner); } -/** - * Function which returns true if 'cq' uses features that are currently supported in SBE without - * 'featureFlagSbeFull' being set; false otherwise. - */ -bool shouldUseRegularSbe(OperationContext* opCtx, - const CanonicalQuery& cq, - const CollectionPtr& mainCollection, - const bool sbeFull) { - // When featureFlagSbeFull is not enabled, we cannot use SBE unless 'trySbeEngine' is enabled or - // if 'trySbeRestricted' is enabled, and we have eligible pushed down stages in the cq pipeline. - auto& queryKnob = cq.getExpCtx()->getQueryKnobConfiguration(); - if (!queryKnob.canPushDownFullyCompatibleStages() && cq.cqPipeline().empty()) { - return false; - } - - if (mainCollection && mainCollection->isTimeseriesCollection() && cq.cqPipeline().empty()) { - // TS queries only use SBE when there's a pipeline. - return false; - } - - // Return true if all the expressions in the CanonicalQuery's filter and projection are SBE - // compatible. - SbeCompatibility minRequiredCompatibility = - getMinRequiredSbeCompatibility(queryKnob.getInternalQueryFrameworkControlForOp(), sbeFull); - return cq.getExpCtx()->getSbeCompatibility() >= minRequiredCompatibility; -} - bool shouldUseSbePlanCache(const QueryPlannerParams& params) { // The logic in this funtion depends on the fact that we clear the SBE plan cache on index // creation. @@ -1233,148 +1175,8 @@ bool shouldUseSbePlanCache(const QueryPlannerParams& params) { } return true; } - -boost::optional getScopedCollectionFilter( - OperationContext* opCtx, - const MultipleCollectionAccessor& collections, - const QueryPlannerParams& plannerParams) { - if (plannerParams.mainCollectionInfo.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { - auto collFilter = collections.getMainCollectionPtrOrAcquisition().getShardingFilter(); - tassert(11321302, - "Attempting to use shard filter when there's no shard filter available for " - "the collection", - collFilter); - return collFilter; - } - return boost::none; -} - } // namespace -/** - * Returns true iff 'descriptor' has fields A and B where all of the following hold - * - * - A is a path prefix of B - * - A is a hashed field in the index - * - B is a non-hashed field in the index - * - * TODO SERVER-99889 this is a workaround for an SBE stage builder bug. - */ -bool indexHasHashedPathPrefixOfNonHashedPath(const IndexDescriptor* descriptor) { - boost::optional hashedPath; - for (const auto& elt : descriptor->keyPattern()) { - if (elt.valueStringDataSafe() == "hashed") { - // Indexes may only contain one hashed field. - hashedPath = elt.fieldNameStringData(); - break; - } - } - if (hashedPath == boost::none) { - // No hashed fields in the index. - return false; - } - // Check if 'hashedPath' is a path prefix for any field in the index. - for (const auto& elt : descriptor->keyPattern()) { - if (expression::isPathPrefixOf(hashedPath.get(), elt.fieldNameStringData())) { - return true; - } - } - return false; -} - -/** - * Returns true if 'collection' has an index that contains two fields, one of which is a path prefix - * of the other, where the prefix field is hashed. Indexes can only contain one hashed field. - * - * TODO SERVER-99889: At the time of writing, there is a bug in the SBE stage builders that - * constructs ExpressionFieldPaths over hashed values. This leads to wrong query results. - * - * The bug arises for covered index scans where a path P is a non-hashed path in the index and a - * strict prefix P' of P is a hashed path in the index. - */ -bool collectionHasIndexWithHashedPathPrefixOfNonHashedPath(const CollectionPtr& collection, - ExpressionContext* expCtx) { - const IndexCatalog* indexCatalog = collection->getIndexCatalog(); - tassert(10230200, "'CollectionPtr' does not have an 'IndexCatalog'", indexCatalog); - OperationContext* opCtx = expCtx->getOperationContext(); - tassert(10230201, "'ExpressionContext' does not have an 'OperationContext'", opCtx); - std::unique_ptr indexIter = - indexCatalog->getIndexIterator(IndexCatalog::InclusionPolicy::kReady); - while (indexIter->more()) { - const IndexCatalogEntry* entry = indexIter->next(); - if (indexHasHashedPathPrefixOfNonHashedPath(entry->descriptor())) { - return true; - } - } - return false; -} - -/** - * Checks if the given query can be executed with the SBE engine based on the canonical query. - * - * This method determines whether the query may be compatible with SBE based only on high-level - * information from the canonical query, before query planning has taken place (such as ineligible - * expressions or collections). - * - * If this method returns true, query planning should be done, followed by another layer of - * validation to make sure the query plan can be executed with SBE. If it returns false, SBE query - * planning can be short-circuited as it is already known that the query is ineligible for SBE. - */ - -bool isQuerySbeCompatible(const CollectionPtr& collection, const CanonicalQuery& cq) { - auto expCtx = cq.getExpCtxRaw(); - - // If we don't support all expressions used or the query is eligible for IDHack, don't use SBE. - if (!expCtx || expCtx->getSbeCompatibility() == SbeCompatibility::notCompatible || - expCtx->getSbePipelineCompatibility() == SbeCompatibility::notCompatible || - (collection && isIdHackEligibleQuery(collection, cq))) { - return false; - } - - const auto* proj = cq.getProj(); - if (proj && (proj->requiresMatchDetails() || proj->containsElemMatch())) { - return false; - } - - // Tailable and resumed scans are not supported either. - if (expCtx->isTailable() || cq.getFindCommandRequest().getRequestResumeToken()) { - return false; - } - - const auto& nss = cq.nss(); - - const auto isTimeseriesColl = collection && collection->isTimeseriesCollection(); - - auto& queryKnob = cq.getExpCtx()->getQueryKnobConfiguration(); - if ((!feature_flags::gFeatureFlagTimeSeriesInSbe.isEnabled() || - queryKnob.getSbeDisableTimeSeriesForOp()) && - isTimeseriesColl) { - return false; - } - - // Queries against the oplog are not supported. Also queries on the inner side of a $lookup are - // not considered for SBE except search queries. - if ((expCtx->getInLookup() && !cq.isSearchQuery()) || nss.isOplog() || - !cq.metadataDeps().none()) { - return false; - } - - - // Queries against collections with a particular shape of compound hashed indexes are not - // supported. - if (collection && collectionHasIndexWithHashedPathPrefixOfNonHashedPath(collection, expCtx)) { - return false; - } - - // Find and aggregate queries with the $_startAt parameter are not supported in SBE. - if (!cq.getFindCommandRequest().getStartAt().isEmpty()) { - return false; - } - - const auto& sortPattern = cq.getSortPattern(); - return !sortPattern || isSortSbeCompatible(*sortPattern); -} - StatusWith> getExecutorFind( OperationContext* opCtx, const MultipleCollectionAccessor& collections, @@ -1412,55 +1214,15 @@ StatusWith> getExecutorFind CurOp::get(opCtx)->stopQueryPlanningTimer(); }); - // First try to use the express id point query fast path. - const auto& mainColl = collections.getMainCollection(); - const auto expressEligibility = isExpressEligible(opCtx, mainColl, *canonicalQuery); - if (expressEligibility == ExpressEligibility::IdPointQueryEligible) { - planCacheCounters.incrementClassicSkippedCounter(); - auto plannerParams = - std::make_unique(QueryPlannerParams::ArgsForExpress{ - opCtx, *canonicalQuery, collections, plannerOptions}); - auto collectionFilter = getScopedCollectionFilter(opCtx, collections, *plannerParams); - const bool isClusteredOnId = plannerParams->clusteredInfo - ? clustered_util::isClusteredOnId(plannerParams->clusteredInfo) - : false; - - auto expressExecutor = isClusteredOnId - ? makeExpressExecutorForFindByClusteredId( - opCtx, - std::move(canonicalQuery), - collections.getMainCollectionPtrOrAcquisition(), - std::move(collectionFilter), - plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA) - : makeExpressExecutorForFindById(opCtx, - std::move(canonicalQuery), - collections.getMainCollectionPtrOrAcquisition(), - std::move(collectionFilter), - plannerOptions & - QueryPlannerParams::RETURN_OWNED_DATA); - - return std::move(expressExecutor); - } - - // The query might still be eligible for express execution via the index equality fast path. - // However, that requires the full set of planner parameters for the main collection to be - // available and creating those now allows them to be reused for subsequent strategies if - // the express index equality one fails. - auto paramsForSingleCollectionQuery = makeQueryPlannerParams(plannerOptions); - if (expressEligibility == ExpressEligibility::IndexedEqualityEligible) { - if (auto indexEntry = - getIndexForExpressEquality(*canonicalQuery, *paramsForSingleCollectionQuery)) { - auto expressExecutor = makeExpressExecutorForFindByUserIndex( - opCtx, - std::move(canonicalQuery), - collections.getMainCollectionPtrOrAcquisition(), - *indexEntry, - getScopedCollectionFilter(opCtx, collections, *paramsForSingleCollectionQuery), - plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA); - - return std::move(expressExecutor); - } + auto expressResult = + tryExpress(opCtx, collections, canonicalQuery, plannerOptions, makeQueryPlannerParams); + if (expressResult.executor) { + return std::move(expressResult.executor); } + // If no express executor was returned, we can reuse the planner params created by `tryExpress` + // for other planning logic. + tassert(11742300, "Expected planner params to be initialized.", expressResult.plannerParams); + auto paramsForSingleCollectionQuery = std::move(expressResult.plannerParams); // Initialize path arrayness in ExpressionContext from the CollectionQueryInfo. // Do not invoke if it has been already initialized. @@ -1471,29 +1233,18 @@ StatusWith> getExecutorFind CollectionQueryInfo::get(collection).getPathArrayness()); } - const bool useSbeEngine = [&] { - const bool forceClassic = - canonicalQuery->getExpCtx()->getQueryKnobConfiguration().isForceClassicEngineEnabled(); - if (forceClassic || !isQuerySbeCompatible(mainColl, *canonicalQuery)) { - return false; - } - - // Add the stages that are candidates for SBE lowering from the 'pipeline' into the - // 'canonicalQuery'. This must be done _before_ checking shouldUseRegularSbe() or - // creating the planner. - auto plannerParams = - std::make_unique(QueryPlannerParams::ArgsForPushDownStagesDecision{ - .opCtx = opCtx, - .canonicalQuery = *canonicalQuery, - .collections = collections, - .plannerOptions = plannerOptions, - }); - attachPipelineStages( - collections, pipeline, needsMerge, canonicalQuery.get(), std::move(plannerParams)); - - const bool sbeFull = feature_flags::gFeatureFlagSbeFull.isEnabled(); - return sbeFull || shouldUseRegularSbe(opCtx, *canonicalQuery, mainColl, sbeFull); - }(); + const bool useSbeEngine = useSbe( + opCtx, + collections, + canonicalQuery.get(), + pipeline, + needsMerge, + std::make_unique(QueryPlannerParams::ArgsForPushDownStagesDecision{ + .opCtx = opCtx, + .canonicalQuery = *canonicalQuery, + .collections = collections, + .plannerOptions = plannerOptions, + })); // If distinct multi-planning is enabled and we have a distinct property, we may not be able to // commit to SBE yet. @@ -1514,7 +1265,6 @@ StatusWith> getExecutorFind finalizePipelineStages(pipeline, canonicalQuery.get()); } - auto makePlanner = [&](std::unique_ptr plannerParams) -> std::unique_ptr { // If we have a distinct, we might get a better plan using classic and DISTINCT_SCAN than @@ -1523,7 +1273,7 @@ StatusWith> getExecutorFind plannerParams->fillOutSecondaryCollectionsPlannerParams( opCtx, *canonicalQuery, collections); - plannerParams->setTargetSbeStageBuilder(opCtx, *canonicalQuery, collections); + plannerParams->setTargetSbeStageBuilder(*canonicalQuery, collections); if (shouldUseSbePlanCache(*plannerParams)) { canonicalQuery->setUsingSbePlanCache(true); @@ -1555,54 +1305,12 @@ StatusWith> getExecutorFind opCtx, collections, canonicalQuery.get(), yieldPolicy, std::move(plannerParams)); }; - auto planner = [&] { - static constexpr size_t kMaxIterations = 5; - for (size_t iter = 0; iter < kMaxIterations; ++iter) { - try { - // First try the single collection query parameters, as these would have been - // generated with query settings if present. - return makePlanner(std::move(paramsForSingleCollectionQuery)); - } catch (const ExceptionFor&) { - // The planner failed to generate a DISTINCT_SCAN for a distinct-like query. Remove - // the distinct property and replan using SBE or subplanning as applicable. - canonicalQuery->resetDistinct(); - if (canonicalQuery->isSbeCompatible()) { - // Stages still need to be finalized for SBE since classic was used previously. - finalizePipelineStages(pipeline, canonicalQuery.get()); - } - return makePlanner(makeQueryPlannerParams(plannerOptions)); - } catch (const ExceptionFor& exception) { - // The planner failed to generate a viable plan. Remove the query settings and - // retry if any are present. Otherwise just propagate the exception. - const auto& querySettings = canonicalQuery->getExpCtx()->getQuerySettings(); - const bool hasQuerySettings = querySettings.getIndexHints().has_value(); - // Planning has been tried without query settings and no execution plan was found. - const bool ignoreQuerySettings = - plannerOptions & QueryPlannerParams::IGNORE_QUERY_SETTINGS; - if (!hasQuerySettings || ignoreQuerySettings) { - throw; - } - LOGV2_DEBUG( - 8524200, - 2, - "Encountered planning error while running with query settings. Retrying " - "without query settings.", - "query"_attr = redact(canonicalQuery->toStringForErrorMsg()), - "querySettings"_attr = querySettings, - "reason"_attr = exception.reason(), - "code"_attr = exception.codeString()); - - plannerOptions |= QueryPlannerParams::IGNORE_QUERY_SETTINGS; - // Propagate the params to the next iteration. - paramsForSingleCollectionQuery = makeQueryPlannerParams(plannerOptions); - } catch (const ExceptionFor&) { - // Propagate the params to the next iteration. - paramsForSingleCollectionQuery = makeQueryPlannerParams(plannerOptions); - canonicalQuery->getExpCtx()->setWasRateLimited(true); - } - } - tasserted(8712800, "Exceeded retry iterations for making a planner"); - }(); + auto planner = retryMakePlanner(std::move(paramsForSingleCollectionQuery), + makeQueryPlannerParams, + makePlanner, + canonicalQuery.get(), + plannerOptions, + pipeline); auto exec = planner->makeExecutor(std::move(canonicalQuery)); return std::move(exec); } diff --git a/src/mongo/db/query/get_executor_fast_paths.cpp b/src/mongo/db/query/get_executor_fast_paths.cpp new file mode 100644 index 00000000000..72cc06a712e --- /dev/null +++ b/src/mongo/db/query/get_executor_fast_paths.cpp @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/query/get_executor_fast_paths.h" + +#include +#include +#include +#include +#include +#include +#include +#include +// IWYU pragma: no_include "ext/alloc_traits.h" +#include "mongo/db/client.h" +#include "mongo/db/curop.h" +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/exec/classic/plan_stage.h" +#include "mongo/db/exec/classic/sort_key_generator.h" +#include "mongo/db/exec/classic/subplan.h" +#include "mongo/db/exec/express/plan_executor_express.h" +#include "mongo/db/exec/runtime_planners/planner_interface.h" +#include "mongo/db/matcher/extensions_callback_real.h" +#include "mongo/db/pipeline/sbe_pushdown.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/compiler/parsers/matcher/expression_parser.h" +#include "mongo/db/query/internal_plans.h" +#include "mongo/db/query/plan_executor.h" +#include "mongo/db/query/planner_analysis.h" +#include "mongo/db/query/query_planner.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/db/query/query_utils.h" +#include "mongo/db/query/wildcard_multikey_paths.h" +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/server_parameter.h" +#include "mongo/db/service_context.h" +#include "mongo/db/shard_role/shard_catalog/index_catalog.h" +#include "mongo/db/shard_role/shard_catalog/index_descriptor.h" +#include "mongo/db/stats/counters.h" +#include "mongo/db/storage/record_store.h" +#include "mongo/db/storage/recovery_unit.h" +#include "mongo/db/update/update_driver.h" +#include "mongo/logv2/log.h" +#include "mongo/util/assert_util.h" + +#include + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery + +namespace mongo { + +namespace { + +boost::optional getScopedCollectionFilter( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + const QueryPlannerParams& plannerParams) { + if (plannerParams.mainCollectionInfo.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { + auto collFilter = collections.getMainCollectionPtrOrAcquisition().getShardingFilter(); + tassert(11321302, + "Attempting to use shard filter when there's no shard filter available for " + "the collection", + collFilter); + return collFilter; + } + return boost::none; +} + +} // namespace + +ExpressResult tryExpress( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + std::unique_ptr& canonicalQuery, + std::size_t plannerOptions, + const std::function(size_t)>& makePlannerParams) { + // First try to use the express id point query fast path. + const auto& mainColl = collections.getMainCollection(); + const auto expressEligibility = isExpressEligible(opCtx, mainColl, *canonicalQuery); + if (expressEligibility == ExpressEligibility::IdPointQueryEligible) { + planCacheCounters.incrementClassicSkippedCounter(); + auto plannerParams = + std::make_unique(QueryPlannerParams::ArgsForExpress{ + opCtx, *canonicalQuery, collections, plannerOptions}); + auto collectionFilter = getScopedCollectionFilter(opCtx, collections, *plannerParams); + const bool isClusteredOnId = plannerParams->clusteredInfo + ? clustered_util::isClusteredOnId(plannerParams->clusteredInfo) + : false; + + auto expressExecutor = isClusteredOnId + ? makeExpressExecutorForFindByClusteredId( + opCtx, + std::move(canonicalQuery), + collections.getMainCollectionPtrOrAcquisition(), + std::move(collectionFilter), + plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA) + : makeExpressExecutorForFindById(opCtx, + std::move(canonicalQuery), + collections.getMainCollectionPtrOrAcquisition(), + std::move(collectionFilter), + plannerOptions & + QueryPlannerParams::RETURN_OWNED_DATA); + + return {.executor = std::move(expressExecutor)}; + } + + // The query might still be eligible for express execution via the index equality fast path. + // However, that requires the full set of planner parameters for the main collection to be + // available and creating those now allows them to be reused for subsequent strategies if + // the express index equality one fails. + auto paramsForSingleCollectionQuery = makePlannerParams(plannerOptions); + if (expressEligibility == ExpressEligibility::IndexedEqualityEligible) { + if (auto indexEntry = + getIndexForExpressEquality(*canonicalQuery, *paramsForSingleCollectionQuery)) { + auto expressExecutor = makeExpressExecutorForFindByUserIndex( + opCtx, + std::move(canonicalQuery), + collections.getMainCollectionPtrOrAcquisition(), + *indexEntry, + getScopedCollectionFilter(opCtx, collections, *paramsForSingleCollectionQuery), + plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA); + + return {.executor = std::move(expressExecutor)}; + } + } + + // Allow reuse of the planner params, in case other planning logic needs it. + return {.plannerParams = std::move(paramsForSingleCollectionQuery)}; +} + +std::unique_ptr tryIdHack( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + CanonicalQuery* cq, + const std::function& makePlannerData) { + const auto& mainCollection = collections.getMainCollection(); + if (!isIdHackEligibleQuery(mainCollection, *cq)) { + return nullptr; + } + + const auto indexEntry = mainCollection->getIndexCatalog()->findIdIndex(opCtx); + if (!indexEntry) { + return nullptr; + } + + LOGV2_DEBUG(20922, + 2, + "Using classic engine idhack", + "canonicalQuery"_attr = redact(cq->toStringShort())); + planCacheCounters.incrementClassicSkippedCounter(); + fastPathQueryCounters.incrementIdHackQueryCounter(); + return std::make_unique(makePlannerData(), indexEntry); +} + +} // namespace mongo diff --git a/src/mongo/db/query/get_executor_fast_paths.h b/src/mongo/db/query/get_executor_fast_paths.h new file mode 100644 index 00000000000..a3a53a179c4 --- /dev/null +++ b/src/mongo/db/query/get_executor_fast_paths.h @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +// IWYU pragma: no_include "ext/alloc_traits.h" +#include "mongo/db/client.h" +#include "mongo/db/curop.h" +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/exec/classic/plan_stage.h" +#include "mongo/db/exec/classic/subplan.h" +#include "mongo/db/exec/runtime_planners/classic_runtime_planner/planner_interface.h" +#include "mongo/db/exec/runtime_planners/classic_runtime_planner_for_sbe/planner_interface.h" +#include "mongo/db/exec/runtime_planners/planner_interface.h" +#include "mongo/db/matcher/extensions_callback_real.h" +#include "mongo/db/pipeline/sbe_pushdown.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/compiler/parsers/matcher/expression_parser.h" +#include "mongo/db/query/internal_plans.h" +#include "mongo/db/query/plan_executor.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/db/query/wildcard_multikey_paths.h" +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/server_parameter.h" +#include "mongo/db/service_context.h" +#include "mongo/db/shard_role/shard_catalog/index_descriptor.h" +#include "mongo/db/storage/record_store.h" +#include "mongo/db/storage/recovery_unit.h" +#include "mongo/db/update/update_driver.h" + + +namespace mongo { + +struct ExpressResult { + std::unique_ptr executor; + std::unique_ptr plannerParams; +}; + +/* + * Builds an express executor if the query is eligible. Otherwise returns the planner params created + * to check express eligibility, for reuse. + */ +ExpressResult tryExpress( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + std::unique_ptr& canonicalQuery, + std::size_t plannerOptions, + const std::function(size_t)>& makePlannerParams); + +/* + * Builds an IdHack planner if eligible, otherwise returns nullptr. + */ +std::unique_ptr tryIdHack( + OperationContext* opCtx, + const MultipleCollectionAccessor& collections, + CanonicalQuery* cq, + const std::function& makePlannerData); + +} // namespace mongo diff --git a/src/mongo/db/query/get_executor_helpers.cpp b/src/mongo/db/query/get_executor_helpers.cpp new file mode 100644 index 00000000000..1d28b1a4906 --- /dev/null +++ b/src/mongo/db/query/get_executor_helpers.cpp @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/query/get_executor_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +// IWYU pragma: no_include "ext/alloc_traits.h" +#include "mongo/db/client.h" +#include "mongo/db/curop.h" +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/exec/classic/plan_stage.h" +#include "mongo/db/exec/classic/sort_key_generator.h" +#include "mongo/db/exec/classic/subplan.h" +#include "mongo/db/matcher/extensions_callback_real.h" +#include "mongo/db/pipeline/sbe_pushdown.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/compiler/parsers/matcher/expression_parser.h" +#include "mongo/db/query/internal_plans.h" +#include "mongo/db/query/planner_analysis.h" +#include "mongo/db/query/query_planner.h" +#include "mongo/db/query/query_utils.h" +#include "mongo/db/query/wildcard_multikey_paths.h" +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/server_parameter.h" +#include "mongo/db/service_context.h" +#include "mongo/db/shard_role/shard_catalog/index_catalog.h" +#include "mongo/db/shard_role/shard_catalog/index_descriptor.h" +#include "mongo/db/storage/record_store.h" +#include "mongo/db/storage/recovery_unit.h" +#include "mongo/db/update/update_driver.h" +#include "mongo/logv2/log.h" +#include "mongo/util/assert_util.h" + + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery + +namespace mongo { + +void setOpDebugPlanCacheInfo(OperationContext* opCtx, const PlanCacheInfo& cacheInfo) { + OpDebug& opDebug = CurOp::get(opCtx)->debug(); + if (!opDebug.planCacheShapeHash && cacheInfo.planCacheShapeHash) { + opDebug.planCacheShapeHash = *cacheInfo.planCacheShapeHash; + } + if (!opDebug.planCacheKey && cacheInfo.planCacheKey) { + opDebug.planCacheKey = *cacheInfo.planCacheKey; + } +} + +std::unique_ptr retryMakePlanner( + std::unique_ptr plannerParams, + const MakePlannerParamsFn& makeQueryPlannerParams, + const MakePlannerFn& makePlanner, + CanonicalQuery* canonicalQuery, + std::size_t plannerOptions, + Pipeline* pipeline) { + static constexpr size_t kMaxIterations = 5; + for (size_t iter = 0; iter < kMaxIterations; ++iter) { + try { + // First try the single collection query parameters, as these would have been + // generated with query settings if present. + return makePlanner(std::move(plannerParams)); + } catch (const ExceptionFor&) { + // The planner failed to generate a DISTINCT_SCAN for a distinct-like query. Remove + // the distinct property and replan using SBE or subplanning as applicable. + canonicalQuery->resetDistinct(); + if (canonicalQuery->isSbeCompatible()) { + // Stages still need to be finalized for SBE since classic was used previously. + finalizePipelineStages(pipeline, canonicalQuery); + } + return makePlanner(makeQueryPlannerParams(plannerOptions)); + } catch (const ExceptionFor& exception) { + // The planner failed to generate a viable plan. Remove the query settings and + // retry if any are present. Otherwise just propagate the exception. + const auto& querySettings = canonicalQuery->getExpCtx()->getQuerySettings(); + const bool hasQuerySettings = querySettings.getIndexHints().has_value(); + // Planning has been tried without query settings and no execution plan was found. + const bool ignoreQuerySettings = + plannerOptions & QueryPlannerParams::IGNORE_QUERY_SETTINGS; + if (!hasQuerySettings || ignoreQuerySettings) { + throw; + } + LOGV2_DEBUG(8524200, + 2, + "Encountered planning error while running with query settings. Retrying " + "without query settings.", + "query"_attr = redact(canonicalQuery->toStringForErrorMsg()), + "querySettings"_attr = querySettings, + "reason"_attr = exception.reason(), + "code"_attr = exception.codeString()); + + plannerOptions |= QueryPlannerParams::IGNORE_QUERY_SETTINGS; + // Propagate the params to the next iteration. + plannerParams = makeQueryPlannerParams(plannerOptions); + } catch (const ExceptionFor&) { + // Propagate the params to the next iteration. + plannerParams = makeQueryPlannerParams(plannerOptions); + canonicalQuery->getExpCtx()->setWasRateLimited(true); + } + } + tasserted(8712800, "Exceeded retry iterations for making a planner"); +} + +} // namespace mongo diff --git a/src/mongo/db/query/get_executor_helpers.h b/src/mongo/db/query/get_executor_helpers.h new file mode 100644 index 00000000000..3c96b411e87 --- /dev/null +++ b/src/mongo/db/query/get_executor_helpers.h @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/base/status_with.h" +#include "mongo/db/curop.h" +#include "mongo/db/exec/classic/delete_stage.h" +#include "mongo/db/exec/runtime_planners/planner_interface.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/pipeline/sbe_pushdown.h" +#include "mongo/db/query/canonical_distinct.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/engine_selection.h" +#include "mongo/db/query/multiple_collection_accessor.h" +#include "mongo/db/query/plan_executor.h" +#include "mongo/db/query/plan_yield_policy.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/db/query/write_ops/canonical_update.h" +#include "mongo/db/query/write_ops/parsed_delete.h" +#include "mongo/db/shard_role/shard_catalog/index_catalog_entry.h" +#include "mongo/db/update/update_driver.h" +#include "mongo/util/modules.h" + +#include +#include + +#include +#include +#include + + +namespace mongo { + +/** + * Struct to hold information about a query plan's cache info. + */ +struct PlanCacheInfo { + boost::optional planCacheKey; + boost::optional planCacheShapeHash; +}; + +/** + * Fills in the given information on the CurOp::OpDebug object, if it has not already been filled in + * by an outer pipeline. + */ +void setOpDebugPlanCacheInfo(OperationContext* opCtx, const PlanCacheInfo& cacheInfo); + + +using MakePlannerParamsFn = std::function(size_t)>; +using MakePlannerFn = + std::function(std::unique_ptr)>; + +/* + * Calls `makePlanner` with five retries in case exceptions are thrown. Uses the given plannerParams + * at first to avoid additional calls to `makeQueryPlannerParams`. + */ +std::unique_ptr retryMakePlanner( + std::unique_ptr plannerParams, + const MakePlannerParamsFn& makeQueryPlannerParams, + const MakePlannerFn& makePlanner, + CanonicalQuery* canonicalQuery, + std::size_t plannerOptions, + Pipeline* pipeline); + +} // namespace mongo diff --git a/src/mongo/db/query/query_planner_params.cpp b/src/mongo/db/query/query_planner_params.cpp index 94e298e4166..d387b9e5013 100644 --- a/src/mongo/db/query/query_planner_params.cpp +++ b/src/mongo/db/query/query_planner_params.cpp @@ -558,8 +558,7 @@ void QueryPlannerParams::fillOutMainCollectionPlannerParams( opCtx, mainColl, &mainCollectionInfo.stats, false /* includeSizeStats */); } -void QueryPlannerParams::setTargetSbeStageBuilder(OperationContext* opCtx, - const CanonicalQuery& canonicalQuery, +void QueryPlannerParams::setTargetSbeStageBuilder(const CanonicalQuery& canonicalQuery, const MultipleCollectionAccessor& collections) { // Set 'TARGET_SBE_STAGE_BUILDER' on the main collection and the secondary collections. We // also update 'providedOptions' in case fillOutSecondaryCollectionsPlannerParams() hasn't diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h index efd0acaa849..ea34f1db417 100644 --- a/src/mongo/db/query/query_planner_params.h +++ b/src/mongo/db/query/query_planner_params.h @@ -358,8 +358,7 @@ struct MONGO_MOD_NEEDS_REPLACEMENT QueryPlannerParams { * This method updates this QueryPlannerParams object as needed so that it can be used with * the SBE engine. */ - void setTargetSbeStageBuilder(OperationContext* opCtx, - const CanonicalQuery& canonicalQuery, + void setTargetSbeStageBuilder(const CanonicalQuery& canonicalQuery, const MultipleCollectionAccessor& collections); /**