SERVER-117423 Factor out common get_executor logic into new files (#47189)
GitOrigin-RevId: cb3802eb34492a3342080ed6842c3fbfeb54752a
This commit is contained in:
parent
8e914b420d
commit
7a1cf68baa
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -49,6 +49,9 @@ filters:
|
||||
- "distinct_access*":
|
||||
approvers:
|
||||
- 10gen/query-optimization
|
||||
- "engine_selection*":
|
||||
approvers:
|
||||
- 10gen/query-execution
|
||||
- "eof_node_type*":
|
||||
approvers:
|
||||
- 10gen/query-optimization
|
||||
|
||||
226
src/mongo/db/query/engine_selection.cpp
Normal file
226
src/mongo/db/query/engine_selection.cpp
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include "mongo/db/query/engine_selection.h"
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/none.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
// 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<StringData> 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<IndexCatalog::IndexIterator> 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<QueryPlannerParams> 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
|
||||
58
src/mongo/db/query/engine_selection.h
Normal file
58
src/mongo/db/query/engine_selection.h
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mongo/db/exec/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 <boost/none.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
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<QueryPlannerParams> plannerParams);
|
||||
|
||||
} // namespace mongo
|
||||
@ -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<ExpressionContext> makeExpressionContextForGetExecutor(
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Struct to hold information about a query plan's cache info.
|
||||
*/
|
||||
struct PlanCacheInfo {
|
||||
boost::optional<uint32_t> planCacheKey;
|
||||
boost::optional<uint32_t> 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<ClassicRuntimePlannerResult> 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<crp_classic::IdHackPlanner>(makePlannerData(), indexEntry);
|
||||
result->runtimePlanner = std::move(idHackPlanner);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1187,33 +1156,6 @@ std::unique_ptr<PlannerInterface> 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<ScopedCollectionFilter> 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<StringData> 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<IndexCatalog::IndexIterator> 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<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind(
|
||||
OperationContext* opCtx,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
@ -1412,55 +1214,15 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> 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>(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<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> 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>(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>(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<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind
|
||||
finalizePipelineStages(pipeline, canonicalQuery.get());
|
||||
}
|
||||
|
||||
|
||||
auto makePlanner = [&](std::unique_ptr<QueryPlannerParams> plannerParams)
|
||||
-> std::unique_ptr<PlannerInterface> {
|
||||
// If we have a distinct, we might get a better plan using classic and DISTINCT_SCAN than
|
||||
@ -1523,7 +1273,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> 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<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> 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<ErrorCodes::NoDistinctScansForDistinctEligibleQuery>&) {
|
||||
// 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<ErrorCodes::NoQueryExecutionPlans>& 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<ErrorCodes::RetryMultiPlanning>&) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
182
src/mongo/db/query/get_executor_fast_paths.cpp
Normal file
182
src/mongo/db/query/get_executor_fast_paths.cpp
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include "mongo/db/query/get_executor_fast_paths.h"
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/none.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
// 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 <utility>
|
||||
|
||||
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery
|
||||
|
||||
namespace mongo {
|
||||
|
||||
namespace {
|
||||
|
||||
boost::optional<ScopedCollectionFilter> 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>& canonicalQuery,
|
||||
std::size_t plannerOptions,
|
||||
const std::function<std::unique_ptr<QueryPlannerParams>(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>(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<classic_runtime_planner::IdHackPlanner> tryIdHack(
|
||||
OperationContext* opCtx,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
CanonicalQuery* cq,
|
||||
const std::function<PlannerData()>& 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<classic_runtime_planner::IdHackPlanner>(makePlannerData(), indexEntry);
|
||||
}
|
||||
|
||||
} // namespace mongo
|
||||
92
src/mongo/db/query/get_executor_fast_paths.h
Normal file
92
src/mongo/db/query/get_executor_fast_paths.h
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/none.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
// 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<PlanExecutor, PlanExecutor::Deleter> executor;
|
||||
std::unique_ptr<QueryPlannerParams> 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>& canonicalQuery,
|
||||
std::size_t plannerOptions,
|
||||
const std::function<std::unique_ptr<QueryPlannerParams>(size_t)>& makePlannerParams);
|
||||
|
||||
/*
|
||||
* Builds an IdHack planner if eligible, otherwise returns nullptr.
|
||||
*/
|
||||
std::unique_ptr<classic_runtime_planner::IdHackPlanner> tryIdHack(
|
||||
OperationContext* opCtx,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
CanonicalQuery* cq,
|
||||
const std::function<PlannerData()>& makePlannerData);
|
||||
|
||||
} // namespace mongo
|
||||
137
src/mongo/db/query/get_executor_helpers.cpp
Normal file
137
src/mongo/db/query/get_executor_helpers.cpp
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include "mongo/db/query/get_executor_helpers.h"
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/none.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
// 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<PlannerInterface> retryMakePlanner(
|
||||
std::unique_ptr<QueryPlannerParams> 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<ErrorCodes::NoDistinctScansForDistinctEligibleQuery>&) {
|
||||
// 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<ErrorCodes::NoQueryExecutionPlans>& 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<ErrorCodes::RetryMultiPlanning>&) {
|
||||
// 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
|
||||
92
src/mongo/db/query/get_executor_helpers.h
Normal file
92
src/mongo/db/query/get_executor_helpers.h
Normal file
@ -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
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mongo/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 <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/none.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
|
||||
namespace mongo {
|
||||
|
||||
/**
|
||||
* Struct to hold information about a query plan's cache info.
|
||||
*/
|
||||
struct PlanCacheInfo {
|
||||
boost::optional<uint32_t> planCacheKey;
|
||||
boost::optional<uint32_t> 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<std::unique_ptr<QueryPlannerParams>(size_t)>;
|
||||
using MakePlannerFn =
|
||||
std::function<std::unique_ptr<PlannerInterface>(std::unique_ptr<QueryPlannerParams>)>;
|
||||
|
||||
/*
|
||||
* 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<PlannerInterface> retryMakePlanner(
|
||||
std::unique_ptr<QueryPlannerParams> plannerParams,
|
||||
const MakePlannerParamsFn& makeQueryPlannerParams,
|
||||
const MakePlannerFn& makePlanner,
|
||||
CanonicalQuery* canonicalQuery,
|
||||
std::size_t plannerOptions,
|
||||
Pipeline* pipeline);
|
||||
|
||||
} // namespace mongo
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user