summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.h7
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp7
-rw-r--r--src/mongo/db/query/classic_stage_builder.h3
-rw-r--r--src/mongo/db/query/get_executor.cpp78
-rw-r--r--src/mongo/db/query/get_executor.h6
-rw-r--r--src/mongo/db/query/multi_collection.h4
-rw-r--r--src/mongo/db/query/planner_analysis.cpp39
-rw-r--r--src/mongo/db/query/planner_analysis.h14
-rw-r--r--src/mongo/db/query/query_planner.cpp41
-rw-r--r--src/mongo/db/query/query_planner.h5
-rw-r--r--src/mongo/db/query/query_planner_group_pushdown_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_params.h5
-rw-r--r--src/mongo/db/query/query_solution.h26
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.cpp14
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.h9
-rw-r--r--src/mongo/db/query/sbe_multi_planner.cpp21
-rw-r--r--src/mongo/db/query/sbe_multi_planner.h5
-rw-r--r--src/mongo/db/query/sbe_runtime_planner.h15
-rw-r--r--src/mongo/db/query/sbe_stage_builder.cpp50
-rw-r--r--src/mongo/db/query/sbe_stage_builder.h8
-rw-r--r--src/mongo/db/query/sbe_stage_builder_test_fixture.cpp2
-rw-r--r--src/mongo/db/query/sbe_sub_planner.cpp24
-rw-r--r--src/mongo/db/query/sbe_sub_planner.h7
-rw-r--r--src/mongo/db/query/stage_builder.h8
-rw-r--r--src/mongo/db/query/stage_builder_util.cpp7
-rw-r--r--src/mongo/db/query/stage_builder_util.h2
26 files changed, 290 insertions, 126 deletions
diff --git a/src/mongo/db/pipeline/document_source_lookup.h b/src/mongo/db/pipeline/document_source_lookup.h
index 3fa95b7e151..20afde99bf3 100644
--- a/src/mongo/db/pipeline/document_source_lookup.h
+++ b/src/mongo/db/pipeline/document_source_lookup.h
@@ -190,6 +190,13 @@ public:
return _localField;
}
+ /**
+ * "as" field must be present in all types of $lookup queries.
+ */
+ const FieldPath& getAsField() const {
+ return _as;
+ }
+
const std::vector<LetVariable>& getLetVariables() const {
return _letVariables;
}
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index ecb2b13d0b5..de8c252d2d9 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -171,8 +171,11 @@ std::vector<std::unique_ptr<InnerPipelineStageInterface>> extractSbeCompatibleSt
feature_flags::gFeatureFlagSBELookupPushdown.isEnabledAndIgnoreFCV() &&
internalEnableMultipleAutoGetCollections.load() && lookupStage->sbeCompatible() &&
!isForeignSharded && !isForeignView;
- uassert(
- 5843700, "$lookup push down logic worked correctly", !lookupEligibleForPushdown);
+ if (lookupEligibleForPushdown) {
+ stagesForPushdown.push_back(std::make_unique<InnerPipelineStageImpl>(lookupStage));
+ sources.erase(itr++);
+ continue;
+ }
break;
}
diff --git a/src/mongo/db/query/classic_stage_builder.h b/src/mongo/db/query/classic_stage_builder.h
index c99531cae95..d761cedfca5 100644
--- a/src/mongo/db/query/classic_stage_builder.h
+++ b/src/mongo/db/query/classic_stage_builder.h
@@ -43,11 +43,12 @@ public:
const CanonicalQuery& cq,
const QuerySolution& solution,
WorkingSet* ws)
- : StageBuilder<PlanStage>{opCtx, collection, cq, solution}, _ws{ws} {}
+ : StageBuilder<PlanStage>{opCtx, cq, solution}, _collection(collection), _ws{ws} {}
std::unique_ptr<PlanStage> build(const QuerySolutionNode* root) final;
private:
+ const CollectionPtr& _collection;
WorkingSet* _ws;
boost::optional<size_t> _ftsKeyPrefixSize;
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 5e545bdb290..160ff81fd8a 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -61,6 +61,7 @@
#include "mongo/db/index_names.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/matcher/extensions_callback_real.h"
+#include "mongo/db/pipeline/document_source_lookup.h"
#include "mongo/db/query/bind_input_params.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/canonical_query_encoder.h"
@@ -377,24 +378,33 @@ void fillOutPlannerParams(OperationContext* opCtx,
}
}
-void fillOutSecondaryCollectionsInformation(OperationContext* opCtx,
- const MultiCollection& collections,
- CanonicalQuery* canonicalQuery,
- QueryPlannerParams* plannerParams) {
+std::map<NamespaceString, SecondaryCollectionInfo> fillOutSecondaryCollectionsInformation(
+ OperationContext* opCtx, const MultiCollection& collections, CanonicalQuery* canonicalQuery) {
+ std::map<NamespaceString, SecondaryCollectionInfo> infoMap;
bool apiStrict = APIParameters::get(opCtx).getAPIStrict().value_or(false);
- for (auto& [collName, secondaryColl] : collections.getSecondaryCollections()) {
- plannerParams->secondaryCollectionsInfo.emplace_back(SecondaryCollectionInfo());
- auto& info = plannerParams->secondaryCollectionsInfo.back();
- info.nss = collName;
+ auto fillOutSecondaryInfo = [&](const NamespaceString& nss,
+ const CollectionPtr& secondaryColl) {
+ auto secondaryInfo = SecondaryCollectionInfo();
if (secondaryColl) {
- fillOutIndexEntries(opCtx, apiStrict, canonicalQuery, secondaryColl, info.indexes);
- info.isSharded = secondaryColl.isSharded();
- info.approximateCollectionSizeBytes = secondaryColl.get()->dataSize(opCtx);
- info.isView = !secondaryColl.get()->getCollectionOptions().viewOn.empty();
+ fillOutIndexEntries(
+ opCtx, apiStrict, canonicalQuery, secondaryColl, secondaryInfo.indexes);
+ secondaryInfo.approximateCollectionSizeBytes = secondaryColl.get()->dataSize(opCtx);
} else {
- info.exists = false;
+ secondaryInfo.exists = false;
}
+ infoMap.emplace(nss, std::move(secondaryInfo));
+ };
+ for (auto& [collName, secondaryColl] : collections.getSecondaryCollections()) {
+ fillOutSecondaryInfo(collName, secondaryColl);
+ }
+
+ // In the event of a self $lookup, we must have an entry for the main collection in the map
+ // of secondary collections.
+ if (collections.hasMainCollection()) {
+ const auto& mainColl = collections.getMainCollection();
+ fillOutSecondaryInfo(mainColl->ns(), mainColl);
}
+ return infoMap;
}
void fillOutPlannerParams(OperationContext* opCtx,
@@ -402,7 +412,8 @@ void fillOutPlannerParams(OperationContext* opCtx,
CanonicalQuery* canonicalQuery,
QueryPlannerParams* plannerParams) {
fillOutPlannerParams(opCtx, collections.getMainCollection(), canonicalQuery, plannerParams);
- fillOutSecondaryCollectionsInformation(opCtx, collections, canonicalQuery, plannerParams);
+ plannerParams->secondaryCollectionsInfo =
+ fillOutSecondaryCollectionsInformation(opCtx, collections, canonicalQuery);
}
bool shouldWaitForOplogVisibility(OperationContext* opCtx,
@@ -572,11 +583,6 @@ public:
*/
virtual const CollectionPtr& getMainCollection() const = 0;
- /**
- * Fills out planning params needed for the target execution engine.
- */
- virtual void fillOutPlannerParamsHelper(QueryPlannerParams* plannerParams) = 0;
-
StatusWith<std::unique_ptr<ResultType>> prepare() {
const auto& mainColl = getMainCollection();
if (!mainColl) {
@@ -599,7 +605,7 @@ public:
// Fill out the planning params. We use these for both cached solutions and non-cached.
QueryPlannerParams plannerParams;
plannerParams.options = _plannerOptions;
- fillOutPlannerParamsHelper(&plannerParams);
+ fillOutPlannerParams(_opCtx, getMainCollection(), _cq, &plannerParams);
tassert(
5842901,
"Fast count queries aren't supported in SBE, therefore, should never lower parts of "
@@ -783,10 +789,6 @@ public:
return _collection;
}
- virtual void fillOutPlannerParamsHelper(QueryPlannerParams* plannerParams) override {
- fillOutPlannerParams(_opCtx, getMainCollection(), _cq, plannerParams);
- }
-
protected:
std::unique_ptr<PlanStage> buildExecutableTree(const QuerySolution& solution) const final {
return stage_builder::buildClassicExecutableTree(_opCtx, _collection, *_cq, solution, _ws);
@@ -962,17 +964,13 @@ public:
return _collections.getMainCollection();
}
- void fillOutPlannerParamsHelper(QueryPlannerParams* plannerParams) override {
- fillOutPlannerParams(_opCtx, _collections, _cq, plannerParams);
- }
-
std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> buildExecutableTree(
const QuerySolution& solution) const final {
// TODO SERVER-62677 We don't pass '_collections' to the function below because at the
// moment, no pushdown is actually happening. This should be changed once the logic for
// pushdown is implemented.
return stage_builder::buildSlotBasedExecutableTree(
- _opCtx, getMainCollection(), *_cq, solution, _yieldPolicy);
+ _opCtx, _collections, *_cq, solution, _yieldPolicy);
}
protected:
@@ -1200,13 +1198,20 @@ std::unique_ptr<sbe::RuntimePlanner> makeRuntimePlannerIfNeeded(
PlanYieldPolicySBE* yieldPolicy,
size_t plannerOptions,
std::unique_ptr<plan_cache_debug_info::DebugInfoSBE> debugInfo) {
- const auto& mainColl = collections.getMainCollection();
// If we have multiple solutions, we always need to do the runtime planning.
if (numSolutions > 1) {
invariant(!needsSubplanning && !decisionWorks);
- return std::make_unique<sbe::MultiPlanner>(
- opCtx, mainColl, *canonicalQuery, PlanCachingMode::AlwaysCache, yieldPolicy);
+ QueryPlannerParams plannerParams;
+ plannerParams.options = plannerOptions;
+ fillOutPlannerParams(opCtx, collections, canonicalQuery, &plannerParams);
+
+ return std::make_unique<sbe::MultiPlanner>(opCtx,
+ collections,
+ *canonicalQuery,
+ plannerParams,
+ PlanCachingMode::AlwaysCache,
+ yieldPolicy);
}
// If the query can be run as sub-queries, the needSubplanning flag will be set to true and
@@ -1220,7 +1225,7 @@ std::unique_ptr<sbe::RuntimePlanner> makeRuntimePlannerIfNeeded(
fillOutPlannerParams(opCtx, collections, canonicalQuery, &plannerParams);
return std::make_unique<sbe::SubPlanner>(
- opCtx, mainColl, *canonicalQuery, plannerParams, yieldPolicy);
+ opCtx, collections, *canonicalQuery, plannerParams, yieldPolicy);
}
invariant(numSolutions == 1);
@@ -1235,7 +1240,7 @@ std::unique_ptr<sbe::RuntimePlanner> makeRuntimePlannerIfNeeded(
fillOutPlannerParams(opCtx, collections, canonicalQuery, &plannerParams);
return std::make_unique<sbe::CachedSolutionPlanner>(opCtx,
- mainColl,
+ collections,
*canonicalQuery,
plannerParams,
*decisionWorks,
@@ -1321,7 +1326,10 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe
invariant(roots.size() == 1);
if (!cq->pipeline().empty()) {
// Need to extend the solution with the agg pipeline and rebuild the execution tree.
- solutions[0] = QueryPlanner::extendWithAggPipeline(*cq, std::move(solutions[0]));
+ solutions[0] = QueryPlanner::extendWithAggPipeline(
+ *cq,
+ std::move(solutions[0]),
+ fillOutSecondaryCollectionsInformation(opCtx, collections, cq.get()));
roots[0] = helper.buildExecutableTree(*(solutions[0]));
}
return plan_executor_factory::make(opCtx,
diff --git a/src/mongo/db/query/get_executor.h b/src/mongo/db/query/get_executor.h
index c06caf23f9d..4e038a646c9 100644
--- a/src/mongo/db/query/get_executor.h
+++ b/src/mongo/db/query/get_executor.h
@@ -84,10 +84,8 @@ void fillOutIndexEntries(OperationContext* opCtx,
/**
* Fills out information about secondary collections held by 'collections' in 'plannerParams'.
*/
-void fillOutSecondaryCollectionsInformation(OperationContext* opCtx,
- const MultiCollection& collections,
- CanonicalQuery* canonicalQuery,
- QueryPlannerParams* plannerParams);
+std::map<NamespaceString, SecondaryCollectionInfo> fillOutSecondaryCollectionsInformation(
+ OperationContext* opCtx, const MultiCollection& collections, CanonicalQuery* canonicalQuery);
/**
* Fill out the provided 'plannerParams' for the 'canonicalQuery' operating on the collection
diff --git a/src/mongo/db/query/multi_collection.h b/src/mongo/db/query/multi_collection.h
index 9b3230a2cc6..1e7a04357af 100644
--- a/src/mongo/db/query/multi_collection.h
+++ b/src/mongo/db/query/multi_collection.h
@@ -50,7 +50,6 @@ public:
secondaryCollCtxes) {
if (mainCollCtx) {
_mainColl = &mainCollCtx->getCollection();
- _mainCollName = mainCollCtx->getNss();
}
for (auto& secondaryColl : secondaryCollCtxes) {
@@ -81,7 +80,7 @@ public:
}
const CollectionPtr& lookupCollection(const NamespaceString& nss) const {
- if (_mainCollName && nss == *_mainCollName) {
+ if (_mainColl && nss == _mainColl->get()->ns()) {
return *_mainColl;
} else if (auto itr = _secondaryColls.find(nss); itr != _secondaryColls.end()) {
return itr->second;
@@ -96,7 +95,6 @@ public:
private:
const CollectionPtr* _mainColl{&CollectionPtr::null};
- boost::optional<NamespaceString> _mainCollName = boost::none;
// Map from namespace to a corresponding CollectionPtr.
std::map<NamespaceString, const CollectionPtr&> _secondaryColls{};
diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp
index c17fd1069d2..3989188faa7 100644
--- a/src/mongo/db/query/planner_analysis.cpp
+++ b/src/mongo/db/query/planner_analysis.cpp
@@ -613,6 +613,45 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::removeProjectSimpleBelowGro
}
// static
+void QueryPlannerAnalysis::determineLookupStrategy(
+ EqLookupNode* eqLookupNode,
+ const std::map<NamespaceString, SecondaryCollectionInfo>& collectionsInfo,
+ bool allowDiskUse) {
+ const auto& foreignCollName = eqLookupNode->foreignCollection;
+ auto foreignCollItr = collectionsInfo.find(NamespaceString(foreignCollName));
+ tassert(5842600,
+ str::stream() << "Expected collection info, but found none; target collection: "
+ << foreignCollName,
+ foreignCollItr != collectionsInfo.end());
+
+ // Does an eligible index exist?
+ // TODO SERVER-62913: finalize the logic for indexes analysis.
+ const auto& foreignField = eqLookupNode->joinFieldForeign;
+ IndexEntry* prefixedIndex = nullptr;
+ for (auto idxEntry : foreignCollItr->second.indexes) {
+ tassert(5842601, "index key pattern should not be empty", !idxEntry.keyPattern.isEmpty());
+ if (idxEntry.keyPattern.firstElement().fieldName() == foreignField) {
+ prefixedIndex = &idxEntry;
+ break;
+ }
+ }
+
+ // TODO SERVER-63449: make this setting configurable and tighten the HJ check to cover the
+ // number of records and the storage size of the collection.
+ static constexpr auto kMaxHashJoinCollectionSize = 100 * 1024 * 1024;
+
+ if (prefixedIndex) {
+ eqLookupNode->lookupStrategy = EqLookupNode::LookupStrategy::kIndexedLoopJoin;
+ eqLookupNode->idxEntry = *prefixedIndex;
+ } else if (allowDiskUse &&
+ foreignCollItr->second.approximateCollectionSizeBytes < kMaxHashJoinCollectionSize) {
+ eqLookupNode->lookupStrategy = EqLookupNode::LookupStrategy::kHashJoin;
+ } else {
+ eqLookupNode->lookupStrategy = EqLookupNode::LookupStrategy::kNestedLoopJoin;
+ }
+}
+
+// static
void QueryPlannerAnalysis::analyzeGeo(const QueryPlannerParams& params,
QuerySolutionNode* solnRoot) {
// Get field names of all 2dsphere indexes with version >= 3.
diff --git a/src/mongo/db/query/planner_analysis.h b/src/mongo/db/query/planner_analysis.h
index e272cae2948..9efc9419ec1 100644
--- a/src/mongo/db/query/planner_analysis.h
+++ b/src/mongo/db/query/planner_analysis.h
@@ -121,6 +121,20 @@ public:
*/
static std::unique_ptr<QuerySolution> removeProjectSimpleBelowGroup(
std::unique_ptr<QuerySolution> soln);
+
+ /**
+ * For the provided 'eqLookupNode', determines what join algorithm should be used to execute it
+ * and marks the node accordingly. In particular:
+ * - An indexed nested loop join is chosen if an index on the foreign collection can be used to
+ * answer the join predicate.
+ * - A hash join is chosen if disk use is allowed and if the foreign collection is sufficiently
+ * small.
+ * - A nested loop join is chosen in all other cases.
+ */
+ static void determineLookupStrategy(
+ EqLookupNode* eqLookupNode,
+ const std::map<NamespaceString, SecondaryCollectionInfo>& collectionsInfo,
+ bool allowDiskUse);
};
} // namespace mongo
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index ad91bd0c323..848e8ba6aef 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -45,6 +45,7 @@
#include "mongo/db/matcher/expression_geo.h"
#include "mongo/db/matcher/expression_text.h"
#include "mongo/db/pipeline/document_source_group.h"
+#include "mongo/db/pipeline/document_source_lookup.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/classic_plan_cache.h"
#include "mongo/db/query/collation/collation_index_key.h"
@@ -1252,7 +1253,9 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
* and later attach the agg portion of the plan to the solution(s) for the "find" part of the query.
*/
std::unique_ptr<QuerySolution> QueryPlanner::extendWithAggPipeline(
- const CanonicalQuery& query, std::unique_ptr<QuerySolution>&& solution) {
+ const CanonicalQuery& query,
+ std::unique_ptr<QuerySolution>&& solution,
+ const std::map<NamespaceString, SecondaryCollectionInfo>& secondaryCollInfos) {
if (query.pipeline().empty()) {
return nullptr;
}
@@ -1260,15 +1263,35 @@ std::unique_ptr<QuerySolution> QueryPlanner::extendWithAggPipeline(
std::unique_ptr<QuerySolutionNode> solnForAgg = std::make_unique<SentinelNode>();
for (auto& innerStage : query.pipeline()) {
auto groupStage = dynamic_cast<DocumentSourceGroup*>(innerStage->documentSource());
- tassert(5842400,
- "Cannot support pushdown of a stage other than $group at the moment",
- groupStage != nullptr);
-
- solnForAgg = std::make_unique<GroupNode>(std::move(solnForAgg),
- groupStage->getIdExpression(),
- groupStage->getAccumulatedFields(),
- groupStage->doingMerge());
+ if (groupStage) {
+ solnForAgg = std::make_unique<GroupNode>(std::move(solnForAgg),
+ groupStage->getIdExpression(),
+ groupStage->getAccumulatedFields(),
+ groupStage->doingMerge());
+ continue;
+ }
+
+ auto lookupStage = dynamic_cast<DocumentSourceLookUp*>(innerStage->documentSource());
+ if (lookupStage) {
+ tassert(5842409,
+ "Only $lookup that use local/foreign field syntax should be lowered",
+ lookupStage->getLocalField() && lookupStage->getForeignField());
+ auto eqLookupNode =
+ std::make_unique<EqLookupNode>(std::move(solnForAgg),
+ lookupStage->getFromNs().toString(),
+ lookupStage->getLocalField()->fullPath(),
+ lookupStage->getForeignField()->fullPath(),
+ lookupStage->getAsField().fullPath());
+ QueryPlannerAnalysis::determineLookupStrategy(
+ eqLookupNode.get(), secondaryCollInfos, query.getExpCtx()->allowDiskUse);
+ solnForAgg = std::move(eqLookupNode);
+ continue;
+ }
+
+ tasserted(5842400,
+ "Cannot support pushdown of a stage other than $group or $lookup at the moment");
}
+
solution->extendWith(std::move(solnForAgg));
return QueryPlannerAnalysis::removeProjectSimpleBelowGroup(std::move(solution));
}
diff --git a/src/mongo/db/query/query_planner.h b/src/mongo/db/query/query_planner.h
index e0de1231f12..170083d523b 100644
--- a/src/mongo/db/query/query_planner.h
+++ b/src/mongo/db/query/query_planner.h
@@ -32,6 +32,7 @@
#include "mongo/base/string_data.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/classic_plan_cache.h"
+#include "mongo/db/query/multi_collection.h"
#include "mongo/db/query/query_planner_params.h"
#include "mongo/db/query/query_solution.h"
@@ -91,7 +92,9 @@ public:
};
static std::unique_ptr<QuerySolution> extendWithAggPipeline(
- const CanonicalQuery& query, std::unique_ptr<QuerySolution>&& solution);
+ const CanonicalQuery& query,
+ std::unique_ptr<QuerySolution>&& solution,
+ const std::map<NamespaceString, SecondaryCollectionInfo>& secondaryCollInfos);
/**
* Returns the list of possible query solutions for the provided 'query' for multi-planning.
diff --git a/src/mongo/db/query/query_planner_group_pushdown_test.cpp b/src/mongo/db/query/query_planner_group_pushdown_test.cpp
index 6476bd6002f..bf1e29b086e 100644
--- a/src/mongo/db/query/query_planner_group_pushdown_test.cpp
+++ b/src/mongo/db/query/query_planner_group_pushdown_test.cpp
@@ -79,7 +79,8 @@ TEST_F(QueryPlannerGroupPushdownTest, PushdownOfASingleGroup) {
// Check the plan after lowering $group into the find subsystem.
ASSERT(!cq->pipeline().empty());
- auto solution = QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]));
+ auto solution =
+ QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]), {} /* secondaryCollInfos */);
ASSERT(QueryPlannerTestLib::solutionMatches(
"{group: {key: {_id: '$_id'}, accs: [{count: {$sum: '$x'}}], node: "
"{cscan: {dir:1, filter: {x:1}}}"
@@ -106,7 +107,8 @@ TEST_F(QueryPlannerGroupPushdownTest, PushdownOfTwoGroups) {
// Check the plan after lowering $group into the find subsystem.
ASSERT(!cq->pipeline().empty());
- auto solution = QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]));
+ auto solution =
+ QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]), {} /* secondaryCollInfos */);
ASSERT(QueryPlannerTestLib::solutionMatches(
"{group: {key: {_id: '$_id'}, accs: [{count: {$min: '$count'}}], node: "
"{group: {key: {_id: '$_id'}, accs: [{count: {$sum: '$x'}}], node: "
@@ -134,7 +136,8 @@ TEST_F(QueryPlannerGroupPushdownTest, PushdownOfOneGroupWithMultipleAccumulators
// Check the plan after lowering $group into the find subsystem.
ASSERT(!cq->pipeline().empty());
- auto solution = QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]));
+ auto solution =
+ QueryPlanner::extendWithAggPipeline(*cq, std::move(solns[0]), {} /* secondaryCollInfos */);
ASSERT(
QueryPlannerTestLib::solutionMatches(
"{group: {key: {_id: '$_id'}, accs: [{count: {$sum: '$x'}}, {m: {$min: '$y'}}], node: "
diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h
index 4fb56c7c213..265f1efaf4a 100644
--- a/src/mongo/db/query/query_planner_params.h
+++ b/src/mongo/db/query/query_planner_params.h
@@ -44,11 +44,8 @@ namespace mongo {
* $lookup) useful for query planning.
*/
struct SecondaryCollectionInfo {
- NamespaceString nss;
std::vector<IndexEntry> indexes{};
bool exists{true};
- bool isSharded{false};
- bool isView{false};
// The approximate size of the collection in bytes.
long long approximateCollectionSizeBytes{0};
@@ -167,7 +164,7 @@ struct QueryPlannerParams {
const CollatorInterface* clusteredCollectionCollator;
// List of information about any secondary collections that can be executed against.
- std::vector<SecondaryCollectionInfo> secondaryCollectionsInfo;
+ std::map<NamespaceString, SecondaryCollectionInfo> secondaryCollectionsInfo;
};
} // namespace mongo
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index d63704a3afa..0eaacc977ed 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -1381,6 +1381,20 @@ struct GroupNode : public QuerySolutionNode {
* by direct name rather than QuerySolutionNode.
*/
struct EqLookupNode : public QuerySolutionNode {
+ /**
+ * Enum describing the possible algorithms that can be used to execute a pushed down $lookup.
+ */
+ enum class LookupStrategy {
+ // Execute the join by storing entries from the foreign collection in a hash table.
+ kHashJoin,
+
+ // Execute the join by doing an index lookup in the foreign collection.
+ kIndexedLoopJoin,
+
+ // Execute the join by iterating over the foreign collection for each local key.
+ kNestedLoopJoin,
+ };
+
EqLookupNode(std::unique_ptr<QuerySolutionNode> child,
const std::string& foreignCollection,
const std::string& joinFieldLocal,
@@ -1445,6 +1459,18 @@ struct EqLookupNode : public QuerySolutionNode {
* If the field already exists in the local (outer) document, the field will be overwritten.
*/
std::string joinField;
+
+ /**
+ * The algorithm that will be used to execute this 'EqLookupNode'. Defaults to nested loop join
+ * as it's applicable independent of collection sizes or the availability of indexes.
+ */
+ LookupStrategy lookupStrategy = LookupStrategy::kNestedLoopJoin;
+
+ /**
+ * The index to be used if we can answer the join predicate with an index on the foreign
+ * collection. Set to 'boost::none' by default and if a non-indexed strategy is chosen.
+ */
+ boost::optional<IndexEntry> idxEntry = boost::none;
};
struct SentinelNode : public QuerySolutionNode {
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp
index d0fd8db0b81..61a2df9f6db 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.cpp
+++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp
@@ -54,9 +54,10 @@ CandidatePlans CachedSolutionPlanner::plan(
// during multiplanning even though multiplanning ran trials of pre-extended plans.
if (!_cq.pipeline().empty()) {
_yieldPolicy->clearRegisteredPlans();
- solutions[0] = QueryPlanner::extendWithAggPipeline(_cq, std::move(solutions[0]));
+ solutions[0] = QueryPlanner::extendWithAggPipeline(
+ _cq, std::move(solutions[0]), _queryParams.secondaryCollectionsInfo);
roots[0] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *solutions[0], _yieldPolicy);
+ _opCtx, _collections, _cq, *solutions[0], _yieldPolicy);
}
const size_t maxReadsBeforeReplan = internalQueryCacheEvictionRatio * _decisionReads;
@@ -172,16 +173,17 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache, std::string reaso
// Therefore, if any of the collection's indexes have been dropped, the query should fail with
// a 'QueryPlanKilled' error.
_indexExistenceChecker.check();
+ const auto& mainColl = _collections.getMainCollection();
if (shouldCache) {
// Deactivate the current cache entry.
- auto cache = CollectionQueryInfo::get(_collection).getPlanCache();
- cache->deactivate(plan_cache_key_factory::make<mongo::PlanCacheKey>(_cq, _collection));
+ auto cache = CollectionQueryInfo::get(mainColl).getPlanCache();
+ cache->deactivate(plan_cache_key_factory::make<mongo::PlanCacheKey>(_cq, mainColl));
}
auto buildExecutableTree = [&](const QuerySolution& sol) {
auto [root, data] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, sol, _yieldPolicy);
+ _opCtx, _collections, _cq, sol, _yieldPolicy);
data.replanReason.emplace(reason);
return std::make_pair(std::move(root), std::move(data));
};
@@ -225,7 +227,7 @@ CandidatePlans CachedSolutionPlanner::replan(bool shouldCache, std::string reaso
const auto cachingMode =
shouldCache ? PlanCachingMode::AlwaysCache : PlanCachingMode::NeverCache;
- MultiPlanner multiPlanner{_opCtx, _collection, _cq, cachingMode, _yieldPolicy};
+ MultiPlanner multiPlanner{_opCtx, _collections, _cq, _queryParams, cachingMode, _yieldPolicy};
auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots));
auto explainer = plan_explainer_factory::make(candidates[winnerIdx].root.get(),
&candidates[winnerIdx].data,
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.h b/src/mongo/db/query/sbe_cached_solution_planner.h
index ebef51e662b..1b3c5cc8008 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.h
+++ b/src/mongo/db/query/sbe_cached_solution_planner.h
@@ -45,14 +45,13 @@ namespace mongo::sbe {
class CachedSolutionPlanner final : public BaseRuntimePlanner {
public:
CachedSolutionPlanner(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QueryPlannerParams& queryParams,
size_t decisionReads,
PlanYieldPolicySBE* yieldPolicy,
std::unique_ptr<plan_cache_debug_info::DebugInfoSBE> debugInfo)
- : BaseRuntimePlanner{opCtx, collection, cq, yieldPolicy},
- _queryParams{queryParams},
+ : BaseRuntimePlanner{opCtx, collections, cq, queryParams, yieldPolicy},
_decisionReads{decisionReads},
_debugInfo{std::move(debugInfo)} {}
@@ -98,10 +97,6 @@ private:
*/
CandidatePlans replan(bool shouldCache, std::string reason) const;
- // Query parameters used to create a query solution when the plan was first created. Used during
- // replanning.
- const QueryPlannerParams _queryParams;
-
// The number of physical reads taken to decide on a winning plan when the plan was first
// cached.
const size_t _decisionReads;
diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp
index e8ddbaa1232..7e7cab6ec32 100644
--- a/src/mongo/db/query/sbe_multi_planner.cpp
+++ b/src/mongo/db/query/sbe_multi_planner.cpp
@@ -50,7 +50,7 @@ CandidatePlans MultiPlanner::plan(
std::move(solutions),
std::move(roots),
trial_period::getTrialPeriodMaxWorks(_opCtx,
- _collection,
+ _collections.getMainCollection(),
internalQueryPlanEvaluationWorksSbe.load(),
internalQueryPlanEvaluationCollFractionSbe.load()));
auto decision = uassertStatusOK(mongo::plan_ranker::pickBestPlan<PlanStageStats>(candidates));
@@ -117,8 +117,12 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans(
}
// Writes a cache entry for the winning plan to the plan cache if possible.
- plan_cache_util::updatePlanCache(
- _opCtx, _collection, _cachingMode, _cq, std::move(decision), candidates);
+ plan_cache_util::updatePlanCache(_opCtx,
+ _collections.getMainCollection(),
+ _cachingMode,
+ _cq,
+ std::move(decision),
+ candidates);
// Extend the winning candidate with the agg pipeline and rebuild the execution tree. Because
// the trial was done with find-only part of the query, we cannot reuse the results. The
@@ -127,9 +131,10 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans(
if (!_cq.pipeline().empty()) {
winner.root->close();
_yieldPolicy->clearRegisteredPlans();
- auto solution = QueryPlanner::extendWithAggPipeline(_cq, std::move(winner.solution));
+ auto solution = QueryPlanner::extendWithAggPipeline(
+ _cq, std::move(winner.solution), _queryParams.secondaryCollectionsInfo);
auto [rootStage, data] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *solution, _yieldPolicy);
+ _opCtx, _collections, _cq, *solution, _yieldPolicy);
rootStage->prepare(data.ctx);
candidates[winnerIdx] = sbe::plan_ranker::CandidatePlan{
std::move(solution), std::move(rootStage), std::move(data)};
@@ -140,10 +145,10 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans(
if (i == winnerIdx)
continue; // have already done the winner
- auto solution =
- QueryPlanner::extendWithAggPipeline(_cq, std::move(candidates[i].solution));
+ auto solution = QueryPlanner::extendWithAggPipeline(
+ _cq, std::move(candidates[i].solution), _queryParams.secondaryCollectionsInfo);
auto&& [rootStage, data] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *solution, _yieldPolicy);
+ _opCtx, _collections, _cq, *solution, _yieldPolicy);
candidates[i] = sbe::plan_ranker::CandidatePlan{
std::move(solution), std::move(rootStage), std::move(data)};
}
diff --git a/src/mongo/db/query/sbe_multi_planner.h b/src/mongo/db/query/sbe_multi_planner.h
index a9aa22bb462..cae5a027f1c 100644
--- a/src/mongo/db/query/sbe_multi_planner.h
+++ b/src/mongo/db/query/sbe_multi_planner.h
@@ -43,11 +43,12 @@ namespace mongo::sbe {
class MultiPlanner final : public BaseRuntimePlanner {
public:
MultiPlanner(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
+ const QueryPlannerParams& qpp,
PlanCachingMode cachingMode,
PlanYieldPolicySBE* yieldPolicy)
- : BaseRuntimePlanner{opCtx, collection, cq, yieldPolicy}, _cachingMode{cachingMode} {}
+ : BaseRuntimePlanner{opCtx, collections, cq, qpp, yieldPolicy}, _cachingMode{cachingMode} {}
CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
diff --git a/src/mongo/db/query/sbe_runtime_planner.h b/src/mongo/db/query/sbe_runtime_planner.h
index 9f4b6805bfe..eef0a59acbc 100644
--- a/src/mongo/db/query/sbe_runtime_planner.h
+++ b/src/mongo/db/query/sbe_runtime_planner.h
@@ -33,6 +33,7 @@
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/query/all_indices_required_checker.h"
#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/multi_collection.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
#include "mongo/db/query/query_solution.h"
#include "mongo/db/query/sbe_plan_ranker.h"
@@ -76,14 +77,16 @@ public:
class BaseRuntimePlanner : public RuntimePlanner {
public:
BaseRuntimePlanner(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
+ const QueryPlannerParams& queryParams,
PlanYieldPolicySBE* yieldPolicy)
: _opCtx(opCtx),
- _collection(collection),
+ _collections(collections),
_cq(cq),
+ _queryParams(queryParams),
_yieldPolicy(yieldPolicy),
- _indexExistenceChecker{collection} {
+ _indexExistenceChecker(collections.getMainCollection()) {
invariant(_opCtx);
}
@@ -130,9 +133,13 @@ protected:
size_t maxTrialPeriodNumReads);
OperationContext* const _opCtx;
- const CollectionPtr& _collection;
+ const MultiCollection& _collections;
const CanonicalQuery& _cq;
+ const QueryPlannerParams _queryParams;
PlanYieldPolicySBE* const _yieldPolicy;
+
+ // TODO SERVER-62913: When support for indexed nested loop join is added, this member needs
+ // to be extended to support checking for index existence on multiple collections.
const AllIndicesRequiredChecker _indexExistenceChecker;
};
} // namespace mongo::sbe
diff --git a/src/mongo/db/query/sbe_stage_builder.cpp b/src/mongo/db/query/sbe_stage_builder.cpp
index c82baaa1235..51f107bd300 100644
--- a/src/mongo/db/query/sbe_stage_builder.cpp
+++ b/src/mongo/db/query/sbe_stage_builder.cpp
@@ -643,12 +643,13 @@ std::unique_ptr<fts::FTSMatcher> makeFtsMatcher(OperationContext* opCtx,
} // namespace
SlotBasedStageBuilder::SlotBasedStageBuilder(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QuerySolution& solution,
PlanYieldPolicySBE* yieldPolicy,
ShardFiltererFactoryInterface* shardFiltererFactory)
- : StageBuilder(opCtx, collection, cq, solution),
+ : StageBuilder(opCtx, cq, solution),
+ _collections(collections),
_yieldPolicy(yieldPolicy),
_data(makeRuntimeEnvironment(_cq, _opCtx, &_slotIdGenerator)),
_shardFiltererFactory(shardFiltererFactory),
@@ -712,8 +713,12 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
auto csn = static_cast<const CollectionScanNode*>(root);
- auto [stage, outputs] = generateCollScan(
- _state, _collection, csn, _yieldPolicy, reqs.getIsTailableCollScanResumeBranch());
+ auto [stage, outputs] =
+ generateCollScan(_state,
+ _collections.lookupCollection(NamespaceString{csn->name}),
+ csn,
+ _yieldPolicy,
+ reqs.getIsTailableCollScanResumeBranch());
if (reqs.has(kReturnKey)) {
// Assign the 'returnKeySlot' to be the empty object.
@@ -828,8 +833,13 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
iamMap = nullptr;
}
- auto [stage, outputs] = generateIndexScan(
- _state, _collection, ixn, indexKeyBitset, _yieldPolicy, iamMap, reqs.has(kIndexKeyPattern));
+ auto [stage, outputs] = generateIndexScan(_state,
+ _collections.getMainCollection(),
+ ixn,
+ indexKeyBitset,
+ _yieldPolicy,
+ iamMap,
+ reqs.has(kIndexKeyPattern));
if (reqs.has(PlanStageSlots::kReturnKey)) {
sbe::EExpression::Vector mkObjArgs;
@@ -889,7 +899,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
}
auto fieldSlotIds = _slotIdGenerator.generateMultiple(csn->fields.size());
- auto stage = std::make_unique<sbe::ColumnScanStage>(_collection->uuid(),
+ auto stage = std::make_unique<sbe::ColumnScanStage>(_collections.getMainCollection()->uuid(),
csn->indexEntry.catalogName,
fieldSlotIds,
csn->fields,
@@ -919,7 +929,7 @@ SlotBasedStageBuilder::makeLoopJoinForFetch(std::unique_ptr<sbe::PlanStage> inpu
indexKeyCorruptionCheckCallback,
std::bind(indexKeyConsistencyCheckCallback, _1, std::move(iamMap), _2, _3, _4, _5, _6));
// Scan the collection in the range [seekKeySlot, Inf).
- auto scanStage = sbe::makeS<sbe::ScanStage>(_collection->uuid(),
+ auto scanStage = sbe::makeS<sbe::ScanStage>(_collections.getMainCollection()->uuid(),
resultSlot,
recordIdSlot,
snapshotIdSlot,
@@ -1850,7 +1860,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder::buildTextMatch(
const QuerySolutionNode* root, const PlanStageReqs& reqs) {
- tassert(5432212, "no collection object", _collection);
+ const auto& mainColl = _collections.getMainCollection();
+ tassert(5432212, "no collection object", mainColl);
tassert(5432213, "index keys requsted for text match node", !reqs.getIndexKeyBitset());
tassert(5432215,
str::stream() << "text match node must have one child, but got "
@@ -1869,7 +1880,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
// Create an FTS 'matcher' to apply 'ftsQuery' to matching documents.
auto matcher = makeFtsMatcher(
- _opCtx, _collection, textNode->index.identifier.catalogName, textNode->ftsQuery.get());
+ _opCtx, mainColl, textNode->index.identifier.catalogName, textNode->ftsQuery.get());
// Build an 'ftsMatch' expression to match a document stored in the 'kResult' slot using the
// 'matcher' instance.
@@ -2631,6 +2642,24 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
return {std::move(outStage), std::move(outputs)};
}
+std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder::buildLookup(
+ const QuerySolutionNode* root, const PlanStageReqs& reqs) {
+ const auto lookupStage = static_cast<const EqLookupNode*>(root);
+ switch (lookupStage->lookupStrategy) {
+ case EqLookupNode::LookupStrategy::kHashJoin:
+ uasserted(5842602, "$lookup planning logic picked hash join");
+ break;
+ case EqLookupNode::LookupStrategy::kIndexedLoopJoin:
+ uasserted(5842603, "$lookup planning logic picked indexed loop join");
+ break;
+ case EqLookupNode::LookupStrategy::kNestedLoopJoin:
+ uasserted(5842604, "$lookup planning logic picked nested loop join");
+ break;
+ default:
+ MONGO_UNREACHABLE_TASSERT(5842605);
+ }
+ MONGO_UNREACHABLE_TASSERT(5842606);
+}
std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots>
SlotBasedStageBuilder::makeUnionForTailableCollScan(const QuerySolutionNode* root,
const PlanStageReqs& reqs) {
@@ -2994,6 +3023,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
{STAGE_AND_SORTED, &SlotBasedStageBuilder::buildAndSorted},
{STAGE_SORT_MERGE, &SlotBasedStageBuilder::buildSortMerge},
{STAGE_GROUP, &SlotBasedStageBuilder::buildGroup},
+ {STAGE_EQ_LOOKUP, &SlotBasedStageBuilder::buildLookup},
{STAGE_SHARDING_FILTER, &SlotBasedStageBuilder::buildShardFilter}};
tassert(4822884,
diff --git a/src/mongo/db/query/sbe_stage_builder.h b/src/mongo/db/query/sbe_stage_builder.h
index 9efd2703504..c004f614046 100644
--- a/src/mongo/db/query/sbe_stage_builder.h
+++ b/src/mongo/db/query/sbe_stage_builder.h
@@ -34,6 +34,7 @@
#include "mongo/db/exec/sbe/values/slot.h"
#include "mongo/db/exec/sbe/values/value.h"
#include "mongo/db/exec/trial_period_utils.h"
+#include "mongo/db/query/multi_collection.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
#include "mongo/db/query/sbe_stage_builder_helpers.h"
#include "mongo/db/query/shard_filterer_factory_interface.h"
@@ -324,7 +325,7 @@ public:
static constexpr StringData kIndexKeyPattern = PlanStageSlots::kIndexKeyPattern;
SlotBasedStageBuilder(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QuerySolution& solution,
PlanYieldPolicySBE* yieldPolicy,
@@ -432,10 +433,15 @@ private:
std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> buildGroup(
const QuerySolutionNode* root, const PlanStageReqs& reqs);
+ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> buildLookup(
+ const QuerySolutionNode* root, const PlanStageReqs& reqs);
+
sbe::value::SlotIdGenerator _slotIdGenerator;
sbe::value::FrameIdGenerator _frameIdGenerator;
sbe::value::SpoolIdGenerator _spoolIdGenerator;
+ const MultiCollection& _collections;
+
PlanYieldPolicySBE* const _yieldPolicy{nullptr};
// Apart from generating just an execution tree, this builder will also produce some auxiliary
diff --git a/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp b/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
index 924c2fe68a1..758f94634f4 100644
--- a/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
@@ -61,7 +61,7 @@ SbeStageBuilderTestFixture::buildPlanStage(
ASSERT_OK(statusWithCQ.getStatus());
stage_builder::SlotBasedStageBuilder builder{opCtx(),
- CollectionPtr::null,
+ MultiCollection(CollectionPtr::null),
*statusWithCQ.getValue(),
*querySolution,
nullptr /* YieldPolicy */,
diff --git a/src/mongo/db/query/sbe_sub_planner.cpp b/src/mongo/db/query/sbe_sub_planner.cpp
index 964c8139ecb..e6830dc5622 100644
--- a/src/mongo/db/query/sbe_sub_planner.cpp
+++ b/src/mongo/db/query/sbe_sub_planner.cpp
@@ -46,12 +46,13 @@ CandidatePlans SubPlanner::plan(
return plan_cache_key_factory::make<mongo::PlanCacheKey>(cq, coll);
};
+ const auto& mainColl = _collections.getMainCollection();
// Plan each branch of the $or.
auto subplanningStatus =
QueryPlanner::planSubqueries(_opCtx,
- CollectionQueryInfo::get(_collection).getPlanCache(),
+ CollectionQueryInfo::get(mainColl).getPlanCache(),
createPlanCacheKey,
- _collection,
+ mainColl,
_cq,
_queryParams);
if (!subplanningStatus.isOK()) {
@@ -71,7 +72,7 @@ CandidatePlans SubPlanner::plan(
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots;
for (auto&& solution : solutions) {
roots.push_back(stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, *cq, *solution, _yieldPolicy));
+ _opCtx, _collections, *cq, *solution, _yieldPolicy));
}
// Clear any plans registered to yield once multiplanning is done for this branch. We don't
@@ -83,7 +84,7 @@ CandidatePlans SubPlanner::plan(
// not use the 'CachedSolutionPlanner' eviction mechanism. We therefore are more
// conservative about putting a potentially bad plan into the cache in the subplan path.
MultiPlanner multiPlanner{
- _opCtx, _collection, *cq, PlanCachingMode::SometimesCache, _yieldPolicy};
+ _opCtx, _collections, *cq, _queryParams, PlanCachingMode::SometimesCache, _yieldPolicy};
auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots));
invariant(winnerIdx < candidates.size());
return std::move(candidates[winnerIdx].solution);
@@ -111,11 +112,12 @@ CandidatePlans SubPlanner::plan(
// If some agg pipeline stages are being pushed down, extend the solution with them.
if (!_cq.pipeline().empty()) {
- compositeSolution = QueryPlanner::extendWithAggPipeline(_cq, std::move(compositeSolution));
+ compositeSolution = QueryPlanner::extendWithAggPipeline(
+ _cq, std::move(compositeSolution), _queryParams.secondaryCollectionsInfo);
}
auto&& [root, data] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *compositeSolution, _yieldPolicy);
+ _opCtx, _collections, _cq, *compositeSolution, _yieldPolicy);
auto status = prepareExecutionPlan(root.get(), &data);
uassertStatusOK(status);
auto [result, recordId, exitedEarly] = status.getValue();
@@ -134,11 +136,12 @@ CandidatePlans SubPlanner::planWholeQuery() const {
if (solutions.size() == 1) {
// If some agg pipeline stages are being pushed down, extend the solution with them.
if (!_cq.pipeline().empty()) {
- solutions[0] = QueryPlanner::extendWithAggPipeline(_cq, std::move(solutions[0]));
+ solutions[0] = QueryPlanner::extendWithAggPipeline(
+ _cq, std::move(solutions[0]), _queryParams.secondaryCollectionsInfo);
}
auto&& [root, data] = stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *solutions[0], _yieldPolicy);
+ _opCtx, _collections, _cq, *solutions[0], _yieldPolicy);
auto status = prepareExecutionPlan(root.get(), &data);
uassertStatusOK(status);
auto [result, recordId, exitedEarly] = status.getValue();
@@ -154,10 +157,11 @@ CandidatePlans SubPlanner::planWholeQuery() const {
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots;
for (auto&& solution : solutions) {
roots.push_back(stage_builder::buildSlotBasedExecutableTree(
- _opCtx, _collection, _cq, *solution, _yieldPolicy));
+ _opCtx, _collections, _cq, *solution, _yieldPolicy));
}
- MultiPlanner multiPlanner{_opCtx, _collection, _cq, PlanCachingMode::AlwaysCache, _yieldPolicy};
+ MultiPlanner multiPlanner{
+ _opCtx, _collections, _cq, _queryParams, PlanCachingMode::AlwaysCache, _yieldPolicy};
return multiPlanner.plan(std::move(solutions), std::move(roots));
}
} // namespace mongo::sbe
diff --git a/src/mongo/db/query/sbe_sub_planner.h b/src/mongo/db/query/sbe_sub_planner.h
index e9c03d3db4b..b81881006d3 100644
--- a/src/mongo/db/query/sbe_sub_planner.h
+++ b/src/mongo/db/query/sbe_sub_planner.h
@@ -45,11 +45,11 @@ namespace mongo::sbe {
class SubPlanner final : public BaseRuntimePlanner {
public:
SubPlanner(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QueryPlannerParams& queryParams,
PlanYieldPolicySBE* yieldPolicy)
- : BaseRuntimePlanner{opCtx, collection, cq, yieldPolicy}, _queryParams{queryParams} {}
+ : BaseRuntimePlanner{opCtx, collections, cq, queryParams, yieldPolicy} {}
CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
@@ -58,8 +58,5 @@ public:
private:
CandidatePlans planWholeQuery() const;
-
- // Query parameters used to create a query solution for each $or branch.
- const QueryPlannerParams _queryParams;
};
} // namespace mongo::sbe
diff --git a/src/mongo/db/query/stage_builder.h b/src/mongo/db/query/stage_builder.h
index 7e20618cd83..41b45c748b6 100644
--- a/src/mongo/db/query/stage_builder.h
+++ b/src/mongo/db/query/stage_builder.h
@@ -40,11 +40,8 @@ namespace mongo::stage_builder {
template <typename PlanStageType>
class StageBuilder {
public:
- StageBuilder(OperationContext* opCtx,
- const CollectionPtr& collection,
- const CanonicalQuery& cq,
- const QuerySolution& solution)
- : _opCtx(opCtx), _collection(collection), _cq(cq), _solution(solution) {}
+ StageBuilder(OperationContext* opCtx, const CanonicalQuery& cq, const QuerySolution& solution)
+ : _opCtx(opCtx), _cq(cq), _solution(solution) {}
virtual ~StageBuilder() = default;
@@ -56,7 +53,6 @@ public:
protected:
OperationContext* _opCtx;
- const CollectionPtr& _collection;
const CanonicalQuery& _cq;
const QuerySolution& _solution;
};
diff --git a/src/mongo/db/query/stage_builder_util.cpp b/src/mongo/db/query/stage_builder_util.cpp
index e9d92bbd6c2..ed41d080153 100644
--- a/src/mongo/db/query/stage_builder_util.cpp
+++ b/src/mongo/db/query/stage_builder_util.cpp
@@ -55,7 +55,7 @@ std::unique_ptr<PlanStage> buildClassicExecutableTree(OperationContext* opCtx,
std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData>
buildSlotBasedExecutableTree(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QuerySolution& solution,
PlanYieldPolicy* yieldPolicy) {
@@ -69,10 +69,11 @@ buildSlotBasedExecutableTree(OperationContext* opCtx,
auto sbeYieldPolicy = dynamic_cast<PlanYieldPolicySBE*>(yieldPolicy);
invariant(sbeYieldPolicy);
- auto shardFilterer = std::make_unique<ShardFiltererFactoryImpl>(collection);
+ auto shardFilterer =
+ std::make_unique<ShardFiltererFactoryImpl>(collections.getMainCollection());
auto builder = std::make_unique<SlotBasedStageBuilder>(
- opCtx, collection, cq, solution, sbeYieldPolicy, shardFilterer.get());
+ opCtx, collections, cq, solution, sbeYieldPolicy, shardFilterer.get());
auto root = builder->build(solution.root());
auto data = builder->getPlanStageData();
diff --git a/src/mongo/db/query/stage_builder_util.h b/src/mongo/db/query/stage_builder_util.h
index b04e533a20e..c16c04fd99b 100644
--- a/src/mongo/db/query/stage_builder_util.h
+++ b/src/mongo/db/query/stage_builder_util.h
@@ -52,7 +52,7 @@ std::unique_ptr<PlanStage> buildClassicExecutableTree(OperationContext* opCtx,
std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData>
buildSlotBasedExecutableTree(OperationContext* opCtx,
- const CollectionPtr& collection,
+ const MultiCollection& collections,
const CanonicalQuery& cq,
const QuerySolution& solution,
PlanYieldPolicy* yieldPolicy);