summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/exec/sort.cpp2
-rw-r--r--src/mongo/db/exec/sort.h8
-rw-r--r--src/mongo/db/exec/sort_executor.cpp30
-rw-r--r--src/mongo/db/exec/sort_executor.h29
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.h37
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.cpp5
-rw-r--r--src/mongo/db/pipeline/document_source_single_document_transformation.h4
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_node.h8
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.cpp24
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.h8
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp74
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp312
-rw-r--r--src/mongo/db/pipeline/pipeline_d.h32
-rw-r--r--src/mongo/db/pipeline/transformer_interface.h14
-rw-r--r--src/mongo/db/query/explain.cpp6
-rw-r--r--src/mongo/db/query/get_executor.cpp8
-rw-r--r--src/mongo/db/query/planner_analysis.cpp6
-rw-r--r--src/mongo/db/query/query_planner.cpp5
-rw-r--r--src/mongo/db/query/query_planner_index_test.cpp14
-rw-r--r--src/mongo/db/query/query_planner_options_test.cpp9
-rw-r--r--src/mongo/db/query/query_planner_params.h25
22 files changed, 275 insertions, 389 deletions
diff --git a/src/mongo/db/exec/sort.cpp b/src/mongo/db/exec/sort.cpp
index eb5486b61ee..a7c4c045234 100644
--- a/src/mongo/db/exec/sort.cpp
+++ b/src/mongo/db/exec/sort.cpp
@@ -111,7 +111,7 @@ std::unique_ptr<PlanStageStats> SortStage::getStats() {
_commonStats.isEOF = isEOF();
std::unique_ptr<PlanStageStats> ret =
std::make_unique<PlanStageStats>(_commonStats, STAGE_SORT);
- ret->specific = _sortExecutor.stats();
+ ret->specific = _sortExecutor.cloneStats();
ret->children.emplace_back(child()->getStats());
return ret;
}
diff --git a/src/mongo/db/exec/sort.h b/src/mongo/db/exec/sort.h
index f437ce0b719..b727771c8bb 100644
--- a/src/mongo/db/exec/sort.h
+++ b/src/mongo/db/exec/sort.h
@@ -72,12 +72,8 @@ public:
std::unique_ptr<PlanStageStats> getStats();
- /**
- * Returns nullptr. Stats related to sort execution must be extracted with 'getStats()', since
- * they are retrieved on demand from the underlying sort execution machinery.
- */
const SpecificStats* getSpecificStats() const final {
- return nullptr;
+ return &_sortExecutor.stats();
}
private:
@@ -86,8 +82,6 @@ private:
SortExecutor _sortExecutor;
- SortStats _specificStats;
-
// Whether or not we have finished loading data into '_sortExecutor'.
bool _populated = false;
};
diff --git a/src/mongo/db/exec/sort_executor.cpp b/src/mongo/db/exec/sort_executor.cpp
index 51e0255a01d..bf2d02a465d 100644
--- a/src/mongo/db/exec/sort_executor.cpp
+++ b/src/mongo/db/exec/sort_executor.cpp
@@ -55,10 +55,13 @@ SortExecutor::SortExecutor(SortPattern sortPattern,
std::string tempDir,
bool allowDiskUse)
: _sortPattern(std::move(sortPattern)),
- _limit(limit),
- _maxMemoryUsageBytes(maxMemoryUsageBytes),
_tempDir(std::move(tempDir)),
- _diskUseAllowed(allowDiskUse) {}
+ _diskUseAllowed(allowDiskUse) {
+ _stats.sortPattern =
+ _sortPattern.serialize(SortPattern::SortKeySerialization::kForExplain).toBson();
+ _stats.limit = limit;
+ _stats.maxMemoryUsageBytes = maxMemoryUsageBytes;
+}
boost::optional<Document> SortExecutor::getNextDoc() {
auto wsm = getNextWsm();
@@ -114,7 +117,7 @@ void SortExecutor::add(Value sortKey, WorkingSetMember data) {
}
_sorter->add(std::move(sortKey), std::move(data));
- _totalDataSizeBytes += data.getMemUsage();
+ _stats.totalDataSizeBytes += data.getMemUsage();
}
void SortExecutor::loadingDone() {
@@ -123,17 +126,17 @@ void SortExecutor::loadingDone() {
_sorter.reset(DocumentSorter::make(makeSortOptions(), Comparator(_sortPattern)));
}
_output.reset(_sorter->done());
- _wasDiskUsed = _wasDiskUsed || _sorter->usedDisk();
+ _stats.wasDiskUsed = _stats.wasDiskUsed || _sorter->usedDisk();
_sorter.reset();
}
SortOptions SortExecutor::makeSortOptions() const {
SortOptions opts;
- if (_limit) {
- opts.limit = _limit;
+ if (_stats.limit) {
+ opts.limit = _stats.limit;
}
- opts.maxMemoryUsageBytes = _maxMemoryUsageBytes;
+ opts.maxMemoryUsageBytes = _stats.maxMemoryUsageBytes;
if (_diskUseAllowed) {
opts.extSortAllowed = true;
opts.tempDir = _tempDir;
@@ -142,15 +145,8 @@ SortOptions SortExecutor::makeSortOptions() const {
return opts;
}
-std::unique_ptr<SortStats> SortExecutor::stats() const {
- auto stats = std::make_unique<SortStats>();
- stats->sortPattern =
- _sortPattern.serialize(SortPattern::SortKeySerialization::kForExplain).toBson();
- stats->limit = _limit;
- stats->maxMemoryUsageBytes = _maxMemoryUsageBytes;
- stats->totalDataSizeBytes = _totalDataSizeBytes;
- stats->wasDiskUsed = _wasDiskUsed;
- return stats;
+std::unique_ptr<SortStats> SortExecutor::cloneStats() const {
+ return std::unique_ptr<SortStats>{static_cast<SortStats*>(_stats.clone())};
}
} // namespace mongo
diff --git a/src/mongo/db/exec/sort_executor.h b/src/mongo/db/exec/sort_executor.h
index c0945fe1fd4..085311e19da 100644
--- a/src/mongo/db/exec/sort_executor.h
+++ b/src/mongo/db/exec/sort_executor.h
@@ -68,20 +68,20 @@ public:
* the smallest limit.
*/
void setLimit(uint64_t limit) {
- if (!_limit || limit < _limit)
- _limit = limit;
+ if (!_stats.limit || limit < _stats.limit)
+ _stats.limit = limit;
}
uint64_t getLimit() const {
- return _limit;
+ return _stats.limit;
}
bool hasLimit() const {
- return _limit > 0;
+ return _stats.limit > 0;
}
bool wasDiskUsed() const {
- return _wasDiskUsed;
+ return _stats.wasDiskUsed;
}
/**
@@ -107,7 +107,11 @@ public:
return _isEOF;
}
- std::unique_ptr<SortStats> stats() const;
+ const SortStats& stats() const {
+ return _stats;
+ }
+
+ std::unique_ptr<SortStats> cloneStats() const;
private:
using DocumentSorter = Sorter<Value, WorkingSetMember>;
@@ -124,18 +128,15 @@ private:
SortOptions makeSortOptions() const;
- SortPattern _sortPattern;
- // A limit of zero is defined as no limit.
- uint64_t _limit;
- uint64_t _maxMemoryUsageBytes;
- std::string _tempDir;
- bool _diskUseAllowed = false;
+ const SortPattern _sortPattern;
+ const std::string _tempDir;
+ const bool _diskUseAllowed;
std::unique_ptr<DocumentSorter> _sorter;
std::unique_ptr<DocumentSorter::Iterator> _output;
+ SortStats _stats;
+
bool _isEOF = false;
- bool _wasDiskUsed = false;
- uint64_t _totalDataSizeBytes = 0u;
};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
index 803f78422b9..6ad22cce49d 100644
--- a/src/mongo/db/pipeline/document_source_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -183,10 +183,6 @@ Value DocumentSourceCursor::serialize(boost::optional<ExplainOptions::Verbosity>
verbosity == pExpCtx->explain);
MutableDocument out;
- out["query"] = Value(_query);
-
- if (!_sort.isEmpty())
- out["sort"] = Value(_sort);
BSONObjBuilder explainStatsBuilder;
diff --git a/src/mongo/db/pipeline/document_source_cursor.h b/src/mongo/db/pipeline/document_source_cursor.h
index eeb03d1ea55..3db747e66db 100644
--- a/src/mongo/db/pipeline/document_source_cursor.h
+++ b/src/mongo/db/pipeline/document_source_cursor.h
@@ -83,36 +83,6 @@ public:
const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
bool trackOplogTimestamp = false);
- /*
- Record the query that was specified for the cursor this wraps, if
- any.
-
- This should be captured after any optimizations are applied to
- the pipeline so that it reflects what is really used.
-
- This gets used for explain output.
-
- @param pBsonObj the query to record
- */
- void setQuery(const BSONObj& query) {
- _query = query;
- }
-
- /*
- Record the sort that was specified for the cursor this wraps, if
- any.
-
- This should be captured after any optimizations are applied to
- the pipeline so that it reflects what is really used.
-
- This gets used for explain output.
-
- @param pBsonObj the sort to record
- */
- void setSort(const BSONObj& sort) {
- _sort = sort;
- }
-
/**
* If subsequent sources need no information from the cursor, the cursor can simply output empty
* documents, avoiding the overhead of converting BSONObjs to Documents.
@@ -133,6 +103,10 @@ public:
return _planSummaryStats;
}
+ bool usedDisk() final {
+ return _planSummaryStats.usedDisk;
+ }
+
protected:
DocumentSourceCursor(Collection* collection,
std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
@@ -183,9 +157,6 @@ private:
// Batches results returned from the underlying PlanExecutor.
std::deque<Document> _currentBatch;
- // BSONObj members must outlive _projection and cursor.
- BSONObj _query;
- BSONObj _sort;
bool _shouldProduceEmptyDocs = false;
// The underlying query plan which feeds this pipeline. Must be destroyed while holding the
diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp
index f8d88637824..2722cc7df7f 100644
--- a/src/mongo/db/pipeline/document_source_lookup.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup.cpp
@@ -265,10 +265,7 @@ DocumentSource::GetNextResult DocumentSourceLookUp::doGetNext() {
objsize <= maxBytes);
results.emplace_back(std::move(*result));
}
- for (auto&& source : pipeline->getSources()) {
- if (source->usedDisk())
- _usedDisk = true;
- }
+ _usedDisk = _usedDisk || pipeline->usedDisk();
MutableDocument output(std::move(inputDoc));
output.setNestedField(_as, Value(std::move(results)));
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.h b/src/mongo/db/pipeline/document_source_single_document_transformation.h
index 6d5daa7171a..8ef435bd776 100644
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.h
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.h
@@ -85,10 +85,6 @@ public:
return *_parsedTransform;
}
- bool isSubsetOfProjection(const BSONObj& proj) const {
- return _parsedTransform->isSubsetOfProjection(proj);
- }
-
protected:
GetNextResult doGetNext() final;
void doDispose() final;
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_node.h b/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
index 92ffff5529d..3e77720858a 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
@@ -126,6 +126,11 @@ public:
void serialize(boost::optional<ExplainOptions::Verbosity> explain,
MutableDocument* output) const;
+ /**
+ * Returns true if this node or any child of this node contains a computed field.
+ */
+ bool subtreeContainsComputedFields() const;
+
protected:
// Returns a unique_ptr to a new instance of the implementing class for the given 'fieldName'.
virtual std::unique_ptr<ProjectionNode> makeChild(std::string fieldName) const = 0;
@@ -182,9 +187,6 @@ private:
// Returns nullptr if no such child exists.
ProjectionNode* getChild(const std::string& field) const;
- // Returns true if this node or any child of this node contains a computed field.
- bool subtreeContainsComputedFields() const;
-
// Our projection semantics are such that all field additions need to be processed in the order
// specified. '_orderToProcessAdditionsAndChildren' tracks that order.
//
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
index 23cc83fabe7..b0f990f753e 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
@@ -216,19 +216,29 @@ void ParsedInclusionProjection::parseSubObject(const BSONObj& subObj,
}
}
-bool ParsedInclusionProjection::isSubsetOfProjection(const BSONObj& proj) const {
+bool ParsedInclusionProjection::isEquivalentToDependencySet(const BSONObj& deps) const {
std::set<std::string> preservedPaths;
_root->reportProjectedPaths(&preservedPaths);
- for (auto&& includedField : preservedPaths) {
- if (!proj.hasField(includedField))
+ size_t numDependencies = 0;
+ for (auto&& dependency : deps) {
+ if (!dependency.trueValue()) {
+ // This is not an included field, so move on.
+ continue;
+ }
+
+ if (preservedPaths.find(dependency.fieldNameStringData().toString()) ==
+ preservedPaths.end()) {
return false;
+ }
+ ++numDependencies;
+ }
+
+ if (numDependencies != preservedPaths.size()) {
+ return false;
}
// If the inclusion has any computed fields or renamed fields, then it's not a subset.
- std::set<std::string> computedPaths;
- StringMap<std::string> renamedPaths;
- _root->reportComputedPaths(&computedPaths, &renamedPaths);
- return computedPaths.empty() && renamedPaths.empty();
+ return !_root->subtreeContainsComputedFields();
}
} // namespace parsed_aggregation_projection
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.h b/src/mongo/db/pipeline/parsed_inclusion_projection.h
index a3f205b90b1..67977e41b4b 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.h
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.h
@@ -175,11 +175,11 @@ public:
Document applyProjection(const Document& inputDoc) const final;
/*
- * Checks whether the inclusion projection represented by the InclusionNode
- * tree is a subset of the object passed in. Projections that have any
- * computed or renamed fields are not considered a subset.
+ * Given 'deps', a BSONObj describing a the dependency set for a pipeline, returns true if this
+ * is an inclusion projection with no computed paths which includes the exact same set of fields
+ * as 'deps'.
*/
- bool isSubsetOfProjection(const BSONObj& proj) const final;
+ bool isEquivalentToDependencySet(const BSONObj& deps) const;
private:
/**
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
index 6412cbdb15e..89e5c180863 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
@@ -832,92 +832,114 @@ TEST(InclusionProjectionExecutionTest, ComputedFieldShouldReplaceNestedArrayForN
}
//
-// Detection of subset projection.
+// Detection of equivalency to the dependency set.
//
-TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForIdenticalProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectEquivalenceForIdenticalProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
- auto proj = BSON("_id" << false << "a" << true << "b" << true);
+ auto proj = BSON("_id" << true << "a" << true << "b" << true);
- ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_TRUE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForSupersetProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldNotDetectEquivalenceForSupersetProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
- auto proj = BSON("_id" << false << "a" << true << "b" << true << "c" << true);
+ auto proj = BSON("_id" << true << "a" << true << "b" << true << "c" << true);
- ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForIdenticalNestedProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectEquivalenceForIdenticalNestedProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << true));
- auto proj = BSON("_id" << false << "a.b" << true);
+ auto proj = BSON("_id" << true << "a.b" << true);
- ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_TRUE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForSupersetProjectionWithNestedFields) {
+TEST(InclusionProjectionExecutionTest,
+ ShouldNotDetectEquivalenceForSupersetProjectionWithNestedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "c" << BSON("d" << true)));
- auto proj = BSON("_id" << false << "a" << true << "b" << true << "c.d" << true);
+ auto proj = BSON("_id" << true << "a" << true << "b" << true << "c.d" << true);
- ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingFields) {
+TEST(InclusionProjectionExecutionTest, ShouldNotDetectEquivalenceForProjectionWithMissingFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
- auto proj = BSON("_id" << false << "a" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ auto proj = BSON("_id" << true << "a" << true);
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
- proj = BSON("_id" << false << "a" << true << "c" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ proj = BSON("_id" << true << "a" << true << "c" << true);
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
TEST(InclusionProjectionExecutionTest,
- ShouldDetectNonSubsetForSupersetProjectionWithoutComputedFields) {
+ ShouldNotDetectEquivalenceForSupersetProjectionWithoutComputedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true << "c" << BSON("$literal" << 1)));
auto proj = BSON("_id" << false << "a" << true << "b" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingNestedFields) {
+TEST(InclusionProjectionExecutionTest,
+ ShouldNotDetectEquivalenceForProjectionWithMissingNestedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << true << "a.c" << true));
auto proj = BSON("_id" << false << "a.b" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithRenamedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldNotDetectEquivalenceForProjectionWithRenamedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a"
<< "$b"));
auto proj = BSON("_id" << false << "b" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
}
-TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingIdField) {
+TEST(InclusionProjectionExecutionTest, ShouldNotDetectEquivalenceForProjectionWithMissingIdField) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true));
auto proj = BSON("a" << true);
- ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
+}
+
+TEST(InclusionProjectionExecutionTest,
+ ShouldNotDetectEquivalenceIfDependenciesExplicitlyExcludeId) {
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
+ inclusion.parse(BSON("a" << true));
+
+ auto proj = BSON("_id" << false << "a" << true);
+
+ ASSERT_FALSE(inclusion.isEquivalentToDependencySet(proj));
+}
+
+TEST(InclusionProjectionExecutionTest,
+ ShouldDetectEquivalenceIfBothDepsAndProjExplicitlyExcludeId) {
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
+ inclusion.parse(BSON("_id" << false << "a" << true));
+
+ auto proj = BSON("_id" << false << "a" << true);
+
+ ASSERT_TRUE(inclusion.isEquivalentToDependencySet(proj));
}
} // namespace
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index a0e6996829e..0ce917467f5 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -66,6 +66,7 @@
#include "mongo/db/pipeline/document_source_sample_from_random_cursor.h"
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/document_source_sort.h"
+#include "mongo/db/pipeline/parsed_inclusion_projection.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/db/query/get_executor.h"
@@ -187,10 +188,9 @@ StatusWith<unique_ptr<PlanExecutor, PlanExecutor::Deleter>> createRandomCursorEx
}
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExecutor(
- OperationContext* opCtx,
+ const intrusive_ptr<ExpressionContext>& expCtx,
Collection* collection,
const NamespaceString& nss,
- const intrusive_ptr<ExpressionContext>& pExpCtx,
BSONObj queryObj,
BSONObj projectionObj,
const QueryMetadataBitSet& metadataRequested,
@@ -201,7 +201,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
const size_t plannerOpts,
const MatchExpressionParser::AllowedFeatureSet& matcherFeatures) {
auto qr = std::make_unique<QueryRequest>(nss);
- qr->setTailableMode(pExpCtx->tailableMode);
+ qr->setTailableMode(expCtx->tailableMode);
qr->setFilter(queryObj);
qr->setProj(projectionObj);
qr->setSort(sortObj);
@@ -214,12 +214,12 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
// The collation on the ExpressionContext has been resolved to either the user-specified
// collation or the collection default. This BSON should never be empty even if the resolved
// collator is simple.
- qr->setCollation(pExpCtx->getCollatorBSON());
+ qr->setCollation(expCtx->getCollatorBSON());
- const ExtensionsCallbackReal extensionsCallback(pExpCtx->opCtx, &nss);
+ const ExtensionsCallbackReal extensionsCallback(expCtx->opCtx, &nss);
auto cq = CanonicalQuery::canonicalize(
- opCtx, std::move(qr), pExpCtx, extensionsCallback, matcherFeatures);
+ expCtx->opCtx, std::move(qr), expCtx, extensionsCallback, matcherFeatures);
if (!cq.isOK()) {
// Return an error instead of uasserting, since there are cases where the combination of
@@ -248,7 +248,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
// example, if we have a document {a: [1,2]} and group by "a" a DISTINCT_SCAN on an "a"
// index would produce one result for '1' and another for '2', which would be incorrect.
auto distinctExecutor =
- getExecutorDistinct(opCtx,
+ getExecutorDistinct(expCtx->opCtx,
collection,
plannerOpts | QueryPlannerParams::STRICT_DISTINCT_ONLY,
&parsedDistinct);
@@ -264,7 +264,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
}
bool permitYield = true;
- return getExecutorFind(opCtx, collection, std::move(cq.getValue()), permitYield, plannerOpts);
+ return getExecutorFind(
+ expCtx->opCtx, collection, std::move(cq.getValue()), permitYield, plannerOpts);
}
/**
@@ -361,13 +362,15 @@ PipelineD::buildInnerQueryExecutor(Collection* collection,
// TODO SERVER-37453 this should no longer be necessary when we no don't need locks
// to destroy a PlanExecutor.
auto deps = pipeline->getDependencies(DepsTracker::kNoMetadata);
+ const bool shouldProduceEmptyDocs = deps.hasNoRequirements();
auto attachExecutorCallback =
- [deps](Collection* collection,
- std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
- Pipeline* pipeline) {
+ [shouldProduceEmptyDocs](
+ Collection* collection,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+ Pipeline* pipeline) {
auto cursor = DocumentSourceCursor::create(
collection, std::move(exec), pipeline->getContext());
- addCursorSource(pipeline, std::move(cursor), std::move(deps));
+ addCursorSource(pipeline, std::move(cursor), shouldProduceEmptyDocs);
};
return std::make_pair(std::move(attachExecutorCallback), std::move(exec));
}
@@ -485,24 +488,10 @@ PipelineD::buildInnerQueryExecutorGeneric(Collection* collection,
}
}
- // Find the set of fields in the source documents depended on by this pipeline.
- DepsTracker deps = pipeline->getDependencies(DocumentSourceMatch::isTextQuery(queryObj)
- ? DepsTracker::kOnlyTextScore
- : DepsTracker::kNoMetadata);
-
- BSONObj projForQuery = deps.toProjectionWithoutMetadata();
-
boost::intrusive_ptr<DocumentSourceSort> sortStage;
boost::intrusive_ptr<DocumentSourceGroup> groupStage;
std::tie(sortStage, groupStage) = getSortAndGroupStagesFromPipeline(pipeline->_sources);
- BSONObj sortObj;
- if (sortStage) {
- sortObj = sortStage->getSortKeyPattern()
- .serialize(SortPattern::SortKeySerialization::kForPipelineSerialization)
- .toBson();
- }
-
std::unique_ptr<GroupFromFirstDocumentTransformation> rewrittenGroupStage;
if (groupStage) {
rewrittenGroupStage = groupStage->rewriteGroupAsTransformOnFirstDocument();
@@ -525,21 +514,26 @@ PipelineD::buildInnerQueryExecutorGeneric(Collection* collection,
// layer, but that is handled elsewhere.
const auto limit = extractLimitForPushdown(pipeline);
+ auto metadataAvailable = DocumentSourceMatch::isTextQuery(queryObj)
+ ? DepsTracker::kOnlyTextScore
+ : DepsTracker::kNoMetadata;
+
// Create the PlanExecutor.
- auto exec = uassertStatusOK(prepareExecutor(expCtx->opCtx,
+ BSONObj projForQuery;
+ bool shouldProduceEmptyDocs = false;
+ auto exec = uassertStatusOK(prepareExecutor(expCtx,
collection,
nss,
pipeline,
- expCtx,
sortStage,
std::move(rewrittenGroupStage),
- deps,
+ metadataAvailable,
queryObj,
limit,
aggRequest,
Pipeline::kAllowedMatcherFeatures,
- &sortObj,
- &projForQuery));
+ &projForQuery,
+ &shouldProduceEmptyDocs));
if (!projForQuery.isEmpty() && !sources.empty()) {
@@ -547,8 +541,14 @@ PipelineD::buildInnerQueryExecutorGeneric(Collection* collection,
// projection generated by the dependency optimization.
auto proj =
dynamic_cast<DocumentSourceSingleDocumentTransformation*>(sources.front().get());
- if (proj && proj->isSubsetOfProjection(projForQuery)) {
- sources.pop_front();
+ if (proj &&
+ proj->getType() == TransformerInterface::TransformerType::kInclusionProjection) {
+ auto&& inclusionProj =
+ static_cast<const parsed_aggregation_projection::ParsedInclusionProjection&>(
+ proj->getTransformer());
+ if (inclusionProj.isEquivalentToDependencySet(projForQuery)) {
+ sources.pop_front();
+ }
}
}
@@ -556,13 +556,13 @@ PipelineD::buildInnerQueryExecutorGeneric(Collection* collection,
const bool trackOplogTS =
(pipeline->peekFront() && pipeline->peekFront()->constraints().isChangeStreamStage());
- auto attachExecutorCallback = [deps, queryObj, sortObj, projForQuery, trackOplogTS](
+ auto attachExecutorCallback = [shouldProduceEmptyDocs, trackOplogTS](
Collection* collection,
std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
Pipeline* pipeline) {
auto cursor = DocumentSourceCursor::create(
collection, std::move(exec), pipeline->getContext(), trackOplogTS);
- addCursorSource(pipeline, std::move(cursor), std::move(deps), queryObj, sortObj);
+ addCursorSource(pipeline, std::move(cursor), shouldProduceEmptyDocs);
};
return std::make_pair(std::move(attachExecutorCallback), std::move(exec));
}
@@ -582,8 +582,6 @@ PipelineD::buildInnerQueryExecutorGeoNear(Collection* collection,
const auto geoNearStage = dynamic_cast<DocumentSourceGeoNear*>(sources.front().get());
invariant(geoNearStage);
- auto deps = pipeline->getDependencies(DepsTracker::kAllGeoNearData);
-
// If the user specified a "key" field, use that field to satisfy the "near" query. Otherwise,
// look for a geo-indexed field in 'collection' that can.
auto nearFieldName =
@@ -594,28 +592,24 @@ PipelineD::buildInnerQueryExecutorGeoNear(Collection* collection,
// Create a PlanExecutor whose query is the "near" predicate on 'nearFieldName' combined with
// the optional "query" argument in the $geoNear stage.
BSONObj fullQuery = geoNearStage->asNearQuery(nearFieldName);
- BSONObj proj = deps.toProjectionWithoutMetadata();
- BSONObj sortFromQuerySystem;
- auto exec = uassertStatusOK(prepareExecutor(expCtx->opCtx,
+
+ BSONObj proj;
+ bool shouldProduceEmptyDocs = false;
+ auto exec = uassertStatusOK(prepareExecutor(expCtx,
collection,
nss,
pipeline,
- expCtx,
nullptr, /* sortStage */
nullptr, /* rewrittenGroupStage */
- deps,
+ DepsTracker::kAllGeoNearData,
std::move(fullQuery),
boost::none, /* limit */
aggRequest,
Pipeline::kGeoNearMatcherFeatures,
- &sortFromQuerySystem,
- &proj));
+ &proj,
+ &shouldProduceEmptyDocs));
- invariant(sortFromQuerySystem.isEmpty(),
- str::stream() << "Unexpectedly got the following sort from the query system: "
- << sortFromQuerySystem.jsonString());
-
- auto attachExecutorCallback = [deps,
+ auto attachExecutorCallback = [shouldProduceEmptyDocs,
distanceField = geoNearStage->getDistanceField(),
locationField = geoNearStage->getLocationField(),
distanceMultiplier =
@@ -629,7 +623,7 @@ PipelineD::buildInnerQueryExecutorGeoNear(Collection* collection,
distanceField,
locationField,
distanceMultiplier);
- addCursorSource(pipeline, std::move(cursor), std::move(deps));
+ addCursorSource(pipeline, std::move(cursor), shouldProduceEmptyDocs);
};
// Remove the initial $geoNear; it will be replaced by $geoNearCursor.
sources.pop_front();
@@ -637,45 +631,23 @@ PipelineD::buildInnerQueryExecutorGeoNear(Collection* collection,
}
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prepareExecutor(
- OperationContext* opCtx,
+ const intrusive_ptr<ExpressionContext>& expCtx,
Collection* collection,
const NamespaceString& nss,
Pipeline* pipeline,
- const intrusive_ptr<ExpressionContext>& expCtx,
const boost::intrusive_ptr<DocumentSourceSort>& sortStage,
std::unique_ptr<GroupFromFirstDocumentTransformation> rewrittenGroupStage,
- const DepsTracker& deps,
+ QueryMetadataBitSet metadataAvailable,
const BSONObj& queryObj,
boost::optional<long long> limit,
const AggregationRequest* aggRequest,
const MatchExpressionParser::AllowedFeatureSet& matcherFeatures,
- BSONObj* sortObj,
- BSONObj* projectionObj) {
- // The query system has the potential to use an index to provide a non-blocking sort and/or to
- // use the projection to generate a covered plan. If this is possible, it is more efficient to
- // let the query system handle those parts of the pipeline. If not, it is more efficient to use
- // a $sort and/or a $project. Thus, we will determine whether the query system can
- // provide a non-blocking sort or a covered projection before we commit to a PlanExecutor.
- //
- // To determine if the query system can provide a non-blocking sort, we pass the
- // NO_BLOCKING_SORT planning option, meaning 'getExecutor' will not produce a PlanExecutor if
- // the query system would use a blocking sort stage.
- //
- // To determine if the query system can provide a covered projection, we pass the
- // NO_UNCOVERED_PROJECTS planning option, meaning 'getExecutor' will not produce a PlanExecutor
- // if the query system would need to fetch the document to do the projection. The following
- // logic uses the above strategies, with multiple calls to 'attemptToGetExecutor' to determine
- // the most efficient way to handle the $sort and $project stages.
- //
- // LATER - We should attempt to determine if the results from the query are returned in some
- // order so we can then apply other optimizations there are tickets for, such as SERVER-4507.
- size_t plannerOpts = QueryPlannerParams::DEFAULT | QueryPlannerParams::NO_BLOCKING_SORT;
+ BSONObj* projectionObj,
+ bool* hasNoRequirements) {
+ invariant(projectionObj);
+ invariant(hasNoRequirements);
- if (deps.hasNoRequirements()) {
- // If we don't need any fields from the input document, performing a count is faster, and
- // will output empty documents, which is okay.
- plannerOpts |= QueryPlannerParams::IS_COUNT;
- }
+ size_t plannerOpts = QueryPlannerParams::DEFAULT;
if (pipeline->peekFront() && pipeline->peekFront()->constraints().isChangeStreamStage()) {
invariant(expCtx->tailableMode == TailableModeEnum::kTailableAndAwaitData);
@@ -687,6 +659,43 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
expCtx->use42ChangeStreamSortKeys = true;
}
+ // If there is a sort stage eligible for pushdown, serialize its SortPattern to a BSONObj. The
+ // BSONObj format is currently necessary to request that the sort is computed by the query layer
+ // inside the inner PlanExecutor. We also remove the $sort stage from the Pipeline, since it
+ // will be handled instead by PlanStage execution.
+ BSONObj sortObj;
+ if (sortStage && canSortBePushedDown(sortStage->getSortKeyPattern())) {
+ sortObj = sortStage->getSortKeyPattern()
+ .serialize(SortPattern::SortKeySerialization::kForPipelineSerialization)
+ .toBson();
+
+ // If the $sort has a coalesced $limit, then we push it down as well. Since the $limit was
+ // after a $sort in the pipeline, it should not have been provided by the caller.
+ invariant(!limit);
+ limit = sortStage->getLimit();
+
+ pipeline->popFrontWithName(DocumentSourceSort::kStageName);
+ }
+
+ // Perform dependency analysis. In order to minimize the dependency set, we only analyze the
+ // stages that remain in the pipeline after pushdown. In particular, any dependencies for a
+ // $match or $sort pushed down into the query layer will not be reflected here.
+ auto deps = pipeline->getDependencies(metadataAvailable);
+ *hasNoRequirements = deps.hasNoRequirements();
+ *projectionObj = deps.toProjectionWithoutMetadata();
+
+ // If we're pushing down a sort, and a merge will be required later, then we need the query
+ // system to produce sortKey metadata.
+ if (!sortObj.isEmpty() && expCtx->needsMerge) {
+ deps.setNeedsMetadata(DocumentMetadataFields::kSortKey, true);
+ }
+
+ if (deps.hasNoRequirements()) {
+ // This query might be eligible for count optimizations, since the remaining stages in the
+ // pipeline don't actually need to read any data produced by the query execution layer.
+ plannerOpts |= QueryPlannerParams::IS_COUNT;
+ }
+
if (rewrittenGroupStage) {
BSONObj emptySort;
@@ -695,14 +704,13 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
// attemptToGetExecutor() calls below) causes getExecutorDistinct() to ignore some otherwise
// valid DISTINCT_SCAN plans, so we pass the projection and exclude the
// NO_UNCOVERED_PROJECTIONS planner parameter.
- auto swExecutorGrouped = attemptToGetExecutor(opCtx,
+ auto swExecutorGrouped = attemptToGetExecutor(expCtx,
collection,
nss,
- expCtx,
queryObj,
*projectionObj,
deps.metadataDeps(),
- sortObj ? *sortObj : emptySort,
+ sortObj,
boost::none, /* limit */
rewrittenGroupStage->groupId(),
aggRequest,
@@ -736,10 +744,28 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
}
}
- const BSONObj emptyProjection;
- const BSONObj metaSortProjection = BSON("$sortKey" << BSON("$meta"
- << "sortKey"));
-
+ // Unlike stages such as $match and limit which always get pushed down into the inner
+ // PlanExecutor when present at the front of the pipeline, 'projectionObj' may not always be
+ // pushed down. (Note that 'projectionObj' is generated based on the dependency set, and
+ // therefore is not always identical to a $project stage in the pipeline.) The query system has
+ // the potential to use an index produce a covered plan, computing the projection based on index
+ // keys rather than documents fetched from the collection. If this is possible, it is more
+ // efficient to let the query system handle the projection, since covered plans typically have a
+ // large performance advantage. If not, it is more currently more efficient to compute the
+ // projection in the agg layer.
+ //
+ // To determine if the query system can provide a covered projection, we pass the
+ // NO_UNCOVERED_PROJECTIONS planning option, meaning 'getExecutor' will not produce a
+ // PlanExecutor if the query system would need to fetch the document to do the projection. If
+ // planning fails due to the NO_COVERED_PROJECTIONS option, then we invoke the planner a second
+ // time without passing 'projectionObj', resulting in a plan where the agg layer handles the
+ // projection.
+ //
+ // The only way to get meta information (e.g. the text score) is to let the query system handle
+ // the projection. In all other cases, unless the query system can do an index-covered
+ // projection and avoid going to the raw record at all, it is faster to have the agg system
+ // perform the projection.
+ //
// TODO SERVER-42905: It should be possible to push down all eligible projections to the query
// layer. This code assumes that metadata is passed from the query layer to the DocumentSource
// layer via a projection, which is no longer true.
@@ -747,100 +773,14 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
plannerOpts |= QueryPlannerParams::NO_UNCOVERED_PROJECTIONS;
}
- SortPattern userSortPattern(*sortObj, expCtx);
- if (sortStage && canSortBePushedDown(userSortPattern)) {
- QueryMetadataBitSet needsSortKey;
- needsSortKey.set(DocumentMetadataFields::MetaType::kSortKey);
-
- // If the $sort has a coalesced $limit, then we push it down as well. Since the $limit was
- // after a $sort in the pipeline, it should not have been provided by the caller.
- invariant(!limit);
- auto limitFollowingSort = sortStage->getLimit();
-
- // See if the query system can provide a non-blocking sort.
- auto swExecutorSort =
- attemptToGetExecutor(opCtx,
- collection,
- nss,
- expCtx,
- queryObj,
- BSONObj(), // empty projection
- expCtx->needsMerge ? needsSortKey : DepsTracker::kNoMetadata,
- *sortObj,
- limitFollowingSort,
- boost::none, /* groupIdForDistinctScan */
- aggRequest,
- plannerOpts,
- matcherFeatures);
-
- if (swExecutorSort.isOK()) {
- // Success! Now see if the query system can also cover the projection.
- auto swExecutorSortAndProj =
- attemptToGetExecutor(opCtx,
- collection,
- nss,
- expCtx,
- queryObj,
- *projectionObj,
- deps.metadataDeps(),
- *sortObj,
- limitFollowingSort,
- boost::none, /* groupIdForDistinctScan */
- aggRequest,
- plannerOpts,
- matcherFeatures);
-
- std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec;
- if (swExecutorSortAndProj.isOK()) {
- // Success! We have a non-blocking sort and a covered projection.
- exec = std::move(swExecutorSortAndProj.getValue());
- } else if (swExecutorSortAndProj != ErrorCodes::NoQueryExecutionPlans) {
-
- return swExecutorSortAndProj.getStatus().withContext(
- "Failed to determine whether query system can provide a "
- "covered projection in addition to a non-blocking sort");
- } else {
- // The query system couldn't cover the projection.
- *projectionObj = BSONObj();
- exec = std::move(swExecutorSort.getValue());
- }
-
- // We know the sort (and any $limit which coalesced with the $sort) is being handled by
- // the query system, so remove the $sort stage.
- pipeline->_sources.pop_front();
-
- return std::move(exec);
- } else if (swExecutorSort != ErrorCodes::NoQueryExecutionPlans) {
- return swExecutorSort.getStatus().withContext(
- "Failed to determine whether query system can provide a non-blocking sort");
- }
- }
-
- // Either there was no $sort stage, or the query system could not provide a non-blocking
- // sort.
- *sortObj = BSONObj();
-
- // Since the DocumentSource layer will perform the sort, remove any dependencies we have on the
- // query layer for a sort key.
- QueryMetadataBitSet metadataDepsWithoutSortKey = deps.metadataDeps();
- metadataDepsWithoutSortKey[DocumentMetadataFields::kSortKey] = false;
- if (!metadataDepsWithoutSortKey.any()) {
- // A sort key requirement would have prevented us from being able to add this parameter
- // before, but now we know the query system won't cover the sort, so we will be able to
- // compute the sort key ourselves during the $sort stage, and thus don't need a query
- // projection to do so.
- plannerOpts |= QueryPlannerParams::NO_UNCOVERED_PROJECTIONS;
- }
-
- // See if the query system can cover the projection.
- auto swExecutorProj = attemptToGetExecutor(opCtx,
+ // See if the query layer can use the projection to produce a covered plan.
+ auto swExecutorProj = attemptToGetExecutor(expCtx,
collection,
nss,
- expCtx,
queryObj,
*projectionObj,
- metadataDepsWithoutSortKey,
- *sortObj,
+ deps.metadataDeps(),
+ sortObj,
limit,
boost::none, /* groupIdForDistinctScan */
aggRequest,
@@ -854,18 +794,18 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
"Failed to determine whether query system can provide a covered projection");
}
- // The query system couldn't provide a covered or simple uncovered projection. Do no projections
- // and request no metadata from the query layer.
+ // The query system couldn't generate a covered plan for the projection. Make another attempt
+ // without the projection. We need not request any metadata: if there are metadata dependencies,
+ // then we always push the projection down to the query layer (which is implemented by
+ // refraining from setting the 'NO_UNCOVERED_PROJECTIONS' parameter).
*projectionObj = BSONObj();
- // If this doesn't work, nothing will.
- return attemptToGetExecutor(opCtx,
+ return attemptToGetExecutor(expCtx,
collection,
nss,
- expCtx,
queryObj,
*projectionObj,
DepsTracker::kNoMetadata,
- *sortObj,
+ sortObj,
limit,
boost::none, /* groupIdForDistinctScan */
aggRequest,
@@ -875,16 +815,12 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
void PipelineD::addCursorSource(Pipeline* pipeline,
boost::intrusive_ptr<DocumentSourceCursor> cursor,
- DepsTracker deps,
- const BSONObj& queryObj,
- const BSONObj& sortObj) {
+ bool shouldProduceEmptyDocs) {
// Add the cursor to the pipeline first so that it's correctly disposed of as part of the
// pipeline if an exception is thrown during this method.
pipeline->addInitialSource(cursor);
- cursor->setQuery(queryObj);
- cursor->setSort(sortObj);
- if (deps.hasNoRequirements()) {
+ if (shouldProduceEmptyDocs) {
cursor->shouldProduceEmptyDocs();
}
}
diff --git a/src/mongo/db/pipeline/pipeline_d.h b/src/mongo/db/pipeline/pipeline_d.h
index 836b84301b5..20fe571423c 100644
--- a/src/mongo/db/pipeline/pipeline_d.h
+++ b/src/mongo/db/pipeline/pipeline_d.h
@@ -170,40 +170,42 @@ private:
* an index to provide a more efficient sort or projection, the sort and/or projection will be
* incorporated into the PlanExecutor.
*
- * 'sortObj' will be set to an empty object if the query system cannot provide a non-blocking
- * sort, and 'projectionObj' will be set to an empty object if the query system cannot provide a
- * covered projection.
- *
* Set 'rewrittenGroupStage' when the pipeline uses $match+$sort+$group stages that are
* compatible with a DISTINCT_SCAN plan that visits the first document in each group
* (SERVER-9507).
+ *
+ * This function computes the dependencies of 'pipeline' and attempts to push the dependency set
+ * down to the query layer as a projection. If the dependency set indeed results in a projection
+ * being pushed down to the query layer, this projection is returned in 'projectionObj'. If no
+ * such projection can be pushed down, then 'projectionObj' is set to the empty BSONObj. This
+ * can happen if the query system cannot provide a covered projection.
+ *
+ * Sets the 'hasNoRequirements' out-parameter based on whether the dependency set is both finite
+ * and empty. In this case, the query has count semantics.
*/
static StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> prepareExecutor(
- OperationContext* opCtx,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
Collection* collection,
const NamespaceString& nss,
Pipeline* pipeline,
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
const boost::intrusive_ptr<DocumentSourceSort>& sortStage,
std::unique_ptr<GroupFromFirstDocumentTransformation> rewrittenGroupStage,
- const DepsTracker& deps,
+ QueryMetadataBitSet metadataAvailable,
const BSONObj& queryObj,
boost::optional<long long> limit,
const AggregationRequest* aggRequest,
const MatchExpressionParser::AllowedFeatureSet& matcherFeatures,
- BSONObj* sortObj,
- BSONObj* projectionObj);
+ BSONObj* projectionObj,
+ bool* hasNoRequirements);
/**
- * Adds 'cursor' to the front of 'pipeline', using 'deps' to inform the cursor of its
- * dependencies. If specified, 'queryObj', 'sortObj' and 'projectionObj' are passed to the
- * cursor for explain reporting.
+ * Adds 'cursor' to the front of 'pipeline'. If 'shouldProduceEmptyDocs' is true, then we inform
+ * 'cursor' that this is a count scenario -- the dependency set is fully known and is empty. In
+ * this case, 'cursor' can return a sequence of empty documents for the caller to count.
*/
static void addCursorSource(Pipeline* pipeline,
boost::intrusive_ptr<DocumentSourceCursor> cursor,
- DepsTracker deps,
- const BSONObj& queryObj = BSONObj(),
- const BSONObj& sortObj = BSONObj());
+ bool shouldProduceEmptyDocs);
};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/transformer_interface.h b/src/mongo/db/pipeline/transformer_interface.h
index d82970702b3..d726b0c91ff 100644
--- a/src/mongo/db/pipeline/transformer_interface.h
+++ b/src/mongo/db/pipeline/transformer_interface.h
@@ -67,19 +67,5 @@ public:
*/
virtual Document serializeTransformation(
boost::optional<ExplainOptions::Verbosity> explain) const = 0;
-
- /**
- * Returns true if this transformer is an inclusion projection and is a subset of
- * 'proj', which must be a valid projection specification. For example, if this
- * TransformerInterface represents the inclusion projection
- *
- * {a: 1, b: 1, c: 1}
- *
- * then it is a subset of the projection {a: 1, c: 1}, and this function returns
- * true.
- */
- virtual bool isSubsetOfProjection(const BSONObj& proj) const {
- return false;
- }
};
} // namespace mongo
diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp
index 665d20d549a..3466e16240c 100644
--- a/src/mongo/db/query/explain.cpp
+++ b/src/mongo/db/query/explain.cpp
@@ -31,7 +31,6 @@
#include "mongo/db/query/explain.h"
-#include "mongo/base/owned_pointer_vector.h"
#include "mongo/bson/util/builder.h"
#include "mongo/db/exec/cached_plan.h"
#include "mongo/db/exec/collection_scan.h"
@@ -42,6 +41,7 @@
#include "mongo/db/exec/multi_plan.h"
#include "mongo/db/exec/near.h"
#include "mongo/db/exec/pipeline_proxy.h"
+#include "mongo/db/exec/sort.h"
#include "mongo/db/exec/text.h"
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/keypattern.h"
@@ -983,6 +983,10 @@ void Explain::getSummaryStats(const PlanExecutor& exec, PlanSummaryStats* statsO
if (STAGE_SORT == stages[i]->stageType()) {
statsOut->hasSortStage = true;
+
+ auto sortStage = static_cast<const SortStage*>(stages[i]);
+ auto sortStats = static_cast<const SortStats*>(sortStage->getSpecificStats());
+ statsOut->usedDisk = sortStats->wasDiskUsed;
}
if (STAGE_IXSCAN == stages[i]->stageType()) {
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 9be58ae42b2..f610b940db4 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -1021,6 +1021,14 @@ bool turnIxscanIntoCount(QuerySolution* soln) {
return false;
}
+ // Since count scans return no data, they are always forward scans. Index scans, on the other
+ // hand, may need to scan the index in reverse order in order to obtain a sort. If the index
+ // scan direction is backwards, then we need to swap the start and end of the count scan bounds.
+ if (isn->direction < 0) {
+ startKey.swap(endKey);
+ std::swap(startKeyInclusive, endKeyInclusive);
+ }
+
// Make the count node that we replace the fetch + ixscan with.
CountScanNode* csn = new CountScanNode(isn->index);
csn->startKey = startKey;
diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp
index 27405653ae7..5a0d1c8a18c 100644
--- a/src/mongo/db/query/planner_analysis.cpp
+++ b/src/mongo/db/query/planner_analysis.cpp
@@ -619,12 +619,6 @@ QuerySolutionNode* QueryPlannerAnalysis::analyzeSort(const CanonicalQuery& query
// If we're here, we need to add a sort stage.
- // If we're not allowed to put a blocking sort in, bail out.
- if (params.options & QueryPlannerParams::NO_BLOCKING_SORT) {
- delete solnRoot;
- return nullptr;
- }
-
if (!solnRoot->fetched()) {
const bool sortIsCovered =
std::all_of(sortObj.begin(), sortObj.end(), [solnRoot](BSONElement e) {
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 56bbe55dfe4..46d90750b5c 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -108,9 +108,6 @@ string optionString(size_t options) {
case QueryPlannerParams::INCLUDE_SHARD_FILTER:
ss << "INCLUDE_SHARD_FILTER ";
break;
- case QueryPlannerParams::NO_BLOCKING_SORT:
- ss << "NO_BLOCKING_SORT ";
- break;
case QueryPlannerParams::INDEX_INTERSECTION:
ss << "INDEX_INTERSECTION ";
break;
@@ -854,7 +851,7 @@ StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
if (0 == out.size() && relevantIndices.front().type != IndexType::INDEX_WILDCARD) {
// Push hinted index solution to output list if found. It is possible to end up without
// a solution in the case where a filtering QueryPlannerParams argument, such as
- // NO_BLOCKING_SORT, leads to its exclusion.
+ // NO_UNCOVERED_PROJECTIONS, leads to its exclusion.
auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
if (soln) {
LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
diff --git a/src/mongo/db/query/query_planner_index_test.cpp b/src/mongo/db/query/query_planner_index_test.cpp
index e56222e9657..ea8b176f302 100644
--- a/src/mongo/db/query/query_planner_index_test.cpp
+++ b/src/mongo/db/query/query_planner_index_test.cpp
@@ -585,20 +585,6 @@ TEST_F(QueryPlannerTest, CompoundMultikeyBoundsNoIntersect) {
// QueryPlannerParams option tests
//
-TEST_F(QueryPlannerTest, NoBlockingSortsAllowedTest) {
- params.options = QueryPlannerParams::NO_BLOCKING_SORT;
- runQuerySortProj(BSONObj(), BSON("x" << 1), BSONObj());
- assertNumSolutions(0U);
-
- addIndex(BSON("x" << 1));
-
- runQuerySortProj(BSONObj(), BSON("x" << 1), BSONObj());
- assertNumSolutions(1U);
- assertSolutionExists(
- "{fetch: {filter: null, node: {ixscan: "
- "{filter: null, pattern: {x: 1}}}}}");
-}
-
TEST_F(QueryPlannerTest, NoTableScanBasic) {
params.options = QueryPlannerParams::NO_TABLE_SCAN;
runQuery(BSONObj());
diff --git a/src/mongo/db/query/query_planner_options_test.cpp b/src/mongo/db/query/query_planner_options_test.cpp
index d2cdd50ea78..ef4070a70d8 100644
--- a/src/mongo/db/query/query_planner_options_test.cpp
+++ b/src/mongo/db/query/query_planner_options_test.cpp
@@ -351,15 +351,6 @@ TEST_F(QueryPlannerTest, HintInvalid) {
runInvalidQueryHint(BSONObj(), fromjson("{b: 1}"));
}
-TEST_F(QueryPlannerTest, HintedBlockingSortIndexFilteredOut) {
- params.options = QueryPlannerParams::NO_BLOCKING_SORT;
- addIndex(BSON("a" << 1));
- addIndex(BSON("b" << 1));
- runQueryAsCommand(
- fromjson("{find: 'testns', filter: {a: 1, b: 1}, sort: {b: 1}, hint: {a: 1}}"));
- assertNumSolutions(0U);
-}
-
TEST_F(QueryPlannerTest, HintedNotCoveredProjectionIndexFilteredOut) {
params.options = QueryPlannerParams::NO_UNCOVERED_PROJECTIONS;
addIndex(BSON("a" << 1));
diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h
index 9229dbb06ce..6597211ad7d 100644
--- a/src/mongo/db/query/query_planner_params.h
+++ b/src/mongo/db/query/query_planner_params.h
@@ -65,39 +65,36 @@ struct QueryPlannerParams {
// See the comment on ShardFilterStage for details.
INCLUDE_SHARD_FILTER = 1 << 2,
- // Set this if you don't want any plans with a blocking sort stage. All sorts must be
- // provided by an index.
- NO_BLOCKING_SORT = 1 << 3,
-
// Set this if you want to turn on index intersection.
- INDEX_INTERSECTION = 1 << 4,
+ INDEX_INTERSECTION = 1 << 3,
- // Indicate to the planner that the caller is requesting a count operation, possibly through
- // a count command, or as part of an aggregation pipeline.
- IS_COUNT = 1 << 5,
+ // Indicate to the planner that this query could be eligible for count optimization. For
+ // example, the query {$group: {_id: null, sum: {$sum: 1}}} is a count-like operation and
+ // could be eligible for the COUNT_SCAN.
+ IS_COUNT = 1 << 4,
// Set this if you want to handle batchSize properly with sort(). If limits on SORT
// stages are always actually limits, then this should be left off. If they are
// sometimes to be interpreted as batchSize, then this should be turned on.
- SPLIT_LIMITED_SORT = 1 << 6,
+ SPLIT_LIMITED_SORT = 1 << 5,
// Set this if you don't want any plans with a non-covered projection stage. All projections
// must be provided/covered by an index.
- NO_UNCOVERED_PROJECTIONS = 1 << 7,
+ NO_UNCOVERED_PROJECTIONS = 1 << 6,
// Set this to generate covered whole IXSCAN plans.
- GENERATE_COVERED_IXSCANS = 1 << 8,
+ GENERATE_COVERED_IXSCANS = 1 << 7,
// Set this to track the most recent timestamp seen by this cursor while scanning the oplog.
- TRACK_LATEST_OPLOG_TS = 1 << 9,
+ TRACK_LATEST_OPLOG_TS = 1 << 8,
// Set this so that collection scans on the oplog wait for visibility before reading.
- OPLOG_SCAN_WAIT_FOR_VISIBLE = 1 << 10,
+ OPLOG_SCAN_WAIT_FOR_VISIBLE = 1 << 9,
// Set this so that getExecutorDistinct() will only use a plan that _guarantees_ it will
// return exactly one document per value of the distinct field. See the comments above the
// declaration of getExecutorDistinct() for more detail.
- STRICT_DISTINCT_ONLY = 1 << 11,
+ STRICT_DISTINCT_ONLY = 1 << 10,
};
// See Options enum above.