diff options
author | David Percy <david.percy@mongodb.com> | 2021-01-29 01:12:21 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-06-23 15:52:33 +0000 |
commit | 93e25299fbf8e0279cd855f4ce19becae230d2a1 (patch) | |
tree | 31d6b248add5e63f96dce60abc1f0479cf14ef82 /src/mongo/db | |
parent | 2950f483e8cfa0970d18ae873c3f0461f033fe31 (diff) | |
download | mongo-93e25299fbf8e0279cd855f4ce19becae230d2a1.tar.gz |
SERVER-54128 Don't push down expressions past sort+limit
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/pipeline/pipeline_d.cpp | 39 |
1 files changed, 33 insertions, 6 deletions
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp index 26382290319..afbe455fee4 100644 --- a/src/mongo/db/pipeline/pipeline_d.cpp +++ b/src/mongo/db/pipeline/pipeline_d.cpp @@ -27,6 +27,7 @@ * it in the license file. */ +#include "mongo/db/query/projection_parser.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery #include "mongo/platform/basic.h" @@ -650,11 +651,18 @@ SkipThenLimit extractSkipAndLimitForPushdown(Pipeline* pipeline) { * 2. If there is no inclusion projection at the front of the pipeline, but there is a finite * dependency set, a projection representing this dependency set will be pushed down. * 3. Otherwise, an empty projection is returned and no projection push down will happen. + * + * If 'allowExpressions' is true, the returned projection may include expressions (which can only + * happen in case 1). If 'allowExpressions' is false and the projection we find has expressions, + * then we fall through to case 2 and attempt to push down a pure-inclusion projection based on its + * dependencies. */ -auto buildProjectionForPushdown(const DepsTracker& deps, Pipeline* pipeline) { +auto buildProjectionForPushdown(const DepsTracker& deps, + Pipeline* pipeline, + bool allowExpressions) { auto&& sources = pipeline->getSources(); - // Short-circuit if the pipeline is emtpy, there is no projection and nothing to push down. + // Short-circuit if the pipeline is empty: there is no projection and nothing to push down. if (sources.empty()) { return BSONObj(); } @@ -663,17 +671,24 @@ auto buildProjectionForPushdown(const DepsTracker& deps, Pipeline* pipeline) { exact_pointer_cast<DocumentSourceSingleDocumentTransformation*>(sources.front().get()); projStage) { if (projStage->getType() == TransformerInterface::TransformerType::kInclusionProjection) { - // If there is an inclusion projection at the front of the pipeline, we have case 1. auto projObj = projStage->getTransformer().serializeTransformation(boost::none).toBson(); - sources.pop_front(); - return projObj; + auto projAst = projection_ast::parse(projStage->getContext(), + projObj, + ProjectionPolicies::aggregateProjectionPolicies()); + if (!projAst.hasExpressions() || allowExpressions) { + // If there is an inclusion projection at the front of the pipeline, we have case 1. + sources.pop_front(); + return projObj; + } } } // Depending of whether there is a finite dependency set, either return a projection // representing this dependency set, or an empty BSON, meaning no projection push down will // happen. This covers cases 2 and 3. + if (deps.getNeedsAnyMetadata()) + return BSONObj(); return deps.toProjectionWithoutMetadata(); } } // namespace @@ -902,7 +917,19 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep // Build a BSONObj representing a projection eligible for pushdown. If there is an inclusion // projection at the front of the pipeline, it will be removed and handled by the PlanStage // layer. If a projection cannot be pushed down, an empty BSONObj will be returned. - projObj = buildProjectionForPushdown(deps, pipeline); + + // In most cases .find() behaves as if it evaluates in a predictable order: + // predicate, sort, skip, limit, projection. + // But there is at least one case where it runs the projection before the sort/skip/limit: + // when the predicate has a rooted $or. (In that case we plan each branch of the $or + // separately, using Subplan, and include the projection on each branch.) + + // To work around this behavior, don't allow pushing down expressions if we are also going + // to push down a sort, skip or limit. We don't want the expressions to be evaluated on any + // documents that the sort/skip/limit would have filtered out. (The sort stage can be a + // top-k sort, which both sorts and limits.) + bool allowExpressions = !sortStage && !skipThenLimit.getSkip() && !skipThenLimit.getLimit(); + projObj = buildProjectionForPushdown(deps, pipeline, allowExpressions); plannerOpts |= QueryPlannerParams::RETURN_OWNED_DATA; } |