diff options
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 38 | ||||
-rw-r--r-- | src/mongo/db/query/parsed_projection.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/query/parsed_projection.h | 6 | ||||
-rw-r--r-- | src/mongo/db/query/parsed_projection_test.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/query/planner_analysis.cpp | 68 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_common.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_common.h | 8 | ||||
-rw-r--r-- | src/mongo/db/query/query_request.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/query/query_request.h | 6 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/query/query_solution.h | 29 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/stage_types.h | 1 |
13 files changed, 161 insertions, 88 deletions
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 4b039fa7aaf..4d809846186 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -49,6 +49,7 @@ #include "mongo/db/exec/multi_plan.h" #include "mongo/db/exec/projection.h" #include "mongo/db/exec/record_store_fast_count.h" +#include "mongo/db/exec/return_key.h" #include "mongo/db/exec/shard_filter.h" #include "mongo/db/exec/sort_key_generator.h" #include "mongo/db/exec/subplan.h" @@ -388,23 +389,32 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, std::move(root)); } - // There might be a projection. The idhack stage will always fetch the full - // document, so we don't support covered projections. However, we might use the - // simple inclusion fast path. - if (nullptr != canonicalQuery->getProj()) { - - // Add a SortKeyGeneratorStage if there is a $meta sortKey projection. - if (canonicalQuery->getProj()->wantSortKey()) { - root = std::make_unique<SortKeyGeneratorStage>( - canonicalQuery->getExpCtx(), - std::move(root), - ws, - canonicalQuery->getQueryRequest().getSort()); - } + // Add a SortKeyGeneratorStage if there is a $meta sortKey projection. + if (canonicalQuery->getProj() && canonicalQuery->getProj()->wantSortKey()) { + root = std::make_unique<SortKeyGeneratorStage>( + canonicalQuery->getExpCtx(), + std::move(root), + ws, + canonicalQuery->getQueryRequest().getSort()); + } + if (canonicalQuery->getQueryRequest().returnKey()) { + // If returnKey was requested, add ReturnKeyStage to return only the index keys in the + // resulting documents. If a projection was also specified, it will be ignored, with + // the exception the $meta sortKey projection, which can be used along with the + // returnKey. + root = std::make_unique<ReturnKeyStage>( + opCtx, + QueryPlannerCommon::extractSortKeyMetaFieldsFromProjection( + canonicalQuery->getQueryRequest().getProj()), + ws, + std::move(root)); + } else if (canonicalQuery->getProj()) { + // There might be a projection. The idhack stage will always fetch the full + // document, so we don't support covered projections. However, we might use the + // simple inclusion fast path. // Stuff the right data into the params depending on what proj impl we use. if (canonicalQuery->getProj()->requiresDocument() || - canonicalQuery->getProj()->wantIndexKey() || canonicalQuery->getProj()->wantSortKey() || canonicalQuery->getProj()->hasDottedFieldPath()) { root = std::make_unique<ProjectionStageDefault>( diff --git a/src/mongo/db/query/parsed_projection.cpp b/src/mongo/db/query/parsed_projection.cpp index 359ad5c23d8..165a27e66bb 100644 --- a/src/mongo/db/query/parsed_projection.cpp +++ b/src/mongo/db/query/parsed_projection.cpp @@ -56,8 +56,6 @@ Status ParsedProjection::make(OperationContext* opCtx, IncludeExclude includeExclude = IncludeExclude::kUninitialized; bool requiresDocument = false; - bool hasIndexKeyProjection = false; - bool wantTextScore = false; bool wantGeoNearPoint = false; bool wantGeoNearDistance = false; @@ -161,7 +159,6 @@ Status ParsedProjection::make(OperationContext* opCtx, if (e2.valuestr() != QueryRequest::metaTextScore && e2.valuestr() != QueryRequest::metaRecordId && - e2.valuestr() != QueryRequest::metaIndexKey && e2.valuestr() != QueryRequest::metaGeoNearDistance && e2.valuestr() != QueryRequest::metaGeoNearPoint && e2.valuestr() != QueryRequest::metaSortKey) { @@ -171,8 +168,6 @@ Status ParsedProjection::make(OperationContext* opCtx, // This clobbers everything else. if (e2.valuestr() == QueryRequest::metaTextScore) { wantTextScore = true; - } else if (e2.valuestr() == QueryRequest::metaIndexKey) { - hasIndexKeyProjection = true; } else if (e2.valuestr() == QueryRequest::metaGeoNearDistance) { wantGeoNearDistance = true; } else if (e2.valuestr() == QueryRequest::metaGeoNearPoint) { @@ -268,7 +263,6 @@ Status ParsedProjection::make(OperationContext* opCtx, // Save the raw spec. It should be owned by the QueryRequest. verify(spec.isOwned()); pp->_source = spec; - pp->_returnKey = hasIndexKeyProjection; pp->_requiresDocument = requiresDocument; // Add meta-projections. @@ -308,11 +302,6 @@ Status ParsedProjection::make(OperationContext* opCtx, } } - // returnKey clobbers everything except for sortKey meta-projection. - if (hasIndexKeyProjection && !wantSortKey) { - pp->_requiresDocument = false; - } - *out = pp.release(); return Status::OK(); } diff --git a/src/mongo/db/query/parsed_projection.h b/src/mongo/db/query/parsed_projection.h index 0a5ee6de3c2..1d9063acf05 100644 --- a/src/mongo/db/query/parsed_projection.h +++ b/src/mongo/db/query/parsed_projection.h @@ -100,10 +100,6 @@ public: return _wantGeoNearPoint; } - bool wantIndexKey() const { - return _returnKey; - } - bool wantSortKey() const { return _wantSortKey; } @@ -193,8 +189,6 @@ private: bool _wantGeoNearPoint = false; - bool _returnKey = false; - // Whether this projection includes a sortKey meta-projection. bool _wantSortKey = false; diff --git a/src/mongo/db/query/parsed_projection_test.cpp b/src/mongo/db/query/parsed_projection_test.cpp index 990b665d6ed..8994723a62d 100644 --- a/src/mongo/db/query/parsed_projection_test.cpp +++ b/src/mongo/db/query/parsed_projection_test.cpp @@ -234,7 +234,6 @@ TEST(ParsedProjectionTest, ParsedProjectionDefaults) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } TEST(ParsedProjectionTest, SortKeyMetaProjection) { @@ -247,7 +246,6 @@ TEST(ParsedProjectionTest, SortKeyMetaProjection) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } TEST(ParsedProjectionTest, SortKeyMetaProjectionCovered) { @@ -261,7 +259,6 @@ TEST(ParsedProjectionTest, SortKeyMetaProjectionCovered) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } TEST(ParsedProjectionTest, SortKeyMetaAndSlice) { @@ -276,7 +273,6 @@ TEST(ParsedProjectionTest, SortKeyMetaAndSlice) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } TEST(ParsedProjectionTest, SortKeyMetaAndElemMatch) { @@ -291,7 +287,6 @@ TEST(ParsedProjectionTest, SortKeyMetaAndElemMatch) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } TEST(ParsedProjectionTest, SortKeyMetaAndExclusion) { @@ -305,7 +300,6 @@ TEST(ParsedProjectionTest, SortKeyMetaAndExclusion) { ASSERT_FALSE(parsedProjection->requiresMatchDetails()); ASSERT_FALSE(parsedProjection->wantGeoNearDistance()); ASSERT_FALSE(parsedProjection->wantGeoNearPoint()); - ASSERT_FALSE(parsedProjection->wantIndexKey()); } // diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index 3487e955675..0d09172ad37 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -306,8 +306,8 @@ auto isCoveredOrAlreadyFetched(const vector<StringData>& fields, * Checks all properties that exclude a projection from being simple. */ auto isSimpleProjection(const CanonicalQuery& query) { - return !query.getProj()->wantIndexKey() && !query.getProj()->wantSortKey() && - !query.getProj()->hasDottedFieldPath() && !query.getProj()->requiresDocument(); + return !query.getProj()->wantSortKey() && !query.getProj()->hasDottedFieldPath() && + !query.getProj()->requiresDocument(); } /** @@ -332,6 +332,21 @@ auto produceCoveredKeyObj(QuerySolutionNode* solnRoot) { } /** + * Adds a stage to generate the sort key metadata if there's no sort stage but we have a sortKey + * meta-projection. + */ +std::unique_ptr<QuerySolutionNode> addSortKeyGeneratorStageIfNeeded( + const CanonicalQuery& query, bool hasSortStage, std::unique_ptr<QuerySolutionNode> solnRoot) { + if (!hasSortStage && query.getProj() && query.getProj()->wantSortKey()) { + auto keyGenNode = std::make_unique<SortKeyGeneratorNode>(); + keyGenNode->sortSpec = query.getQueryRequest().getSort(); + keyGenNode->children.push_back(solnRoot.release()); + return keyGenNode; + } + return solnRoot; +} + +/** * When projection needs to be added to the solution tree, this function chooses between the default * implementation and one of the fast paths. */ @@ -340,24 +355,12 @@ std::unique_ptr<ProjectionNode> analyzeProjection(const CanonicalQuery& query, const bool hasSortStage) { const QueryRequest& qr = query.getQueryRequest(); - // If there's no sort stage but we have a sortKey meta-projection, we need to add a stage to - // generate the sort key metadata. - auto addSortKeyGeneratorStageIfNeeded = [&]() { - if (!hasSortStage && query.getProj()->wantSortKey()) { - auto keyGenNode = std::make_unique<SortKeyGeneratorNode>(); - keyGenNode->sortSpec = qr.getSort(); - keyGenNode->children.push_back(solnRoot.release()); - solnRoot = std::move(keyGenNode); - } - }; - LOG(5) << "PROJECTION: Current plan is:\n" << redact(solnRoot->toString()); // If the projection requires the entire document we add a fetch stage if not present. Otherwise - // we add a fetch stage if we are not covered and not returnKey. + // we add a fetch stage if we are not covered. if ((query.getProj()->requiresDocument() && !solnRoot->fetched()) || - (!isCoveredOrAlreadyFetched(query.getProj()->getRequiredFields(), *solnRoot) && - !query.getProj()->wantIndexKey())) { + (!isCoveredOrAlreadyFetched(query.getProj()->getRequiredFields(), *solnRoot))) { auto fetch = std::make_unique<FetchNode>(); fetch->children.push_back(solnRoot.release()); solnRoot = std::move(fetch); @@ -372,30 +375,33 @@ std::unique_ptr<ProjectionNode> analyzeProjection(const CanonicalQuery& query, if (isSimpleProjection(query)) { // If the projection is simple, but not covered, use 'ProjectionNodeSimple'. if (solnRoot->fetched()) { - addSortKeyGeneratorStageIfNeeded(); return std::make_unique<ProjectionNodeSimple>( - std::move(solnRoot), *query.root(), qr.getProj(), *query.getProj()); + addSortKeyGeneratorStageIfNeeded(query, hasSortStage, std::move(solnRoot)), + *query.root(), + qr.getProj(), + *query.getProj()); } else { // If we're here we're not fetched so we're covered. Let's see if we can get out of // using the default projType. If 'solnRoot' is an index scan we can use the faster // covered impl. BSONObj coveredKeyObj = produceCoveredKeyObj(solnRoot.get()); if (!coveredKeyObj.isEmpty()) { - addSortKeyGeneratorStageIfNeeded(); - return std::make_unique<ProjectionNodeCovered>(std::move(solnRoot), - *query.root(), - qr.getProj(), - *query.getProj(), - std::move(coveredKeyObj)); + return std::make_unique<ProjectionNodeCovered>( + addSortKeyGeneratorStageIfNeeded(query, hasSortStage, std::move(solnRoot)), + *query.root(), + qr.getProj(), + *query.getProj(), + std::move(coveredKeyObj)); } } } - addSortKeyGeneratorStageIfNeeded(); return std::make_unique<ProjectionNodeDefault>( - std::move(solnRoot), *query.root(), qr.getProj(), *query.getProj()); + addSortKeyGeneratorStageIfNeeded(query, hasSortStage, std::move(solnRoot)), + *query.root(), + qr.getProj(), + *query.getProj()); } - } // namespace // static @@ -797,7 +803,13 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( } // Project the results. - if (query.getProj()) { + if (qr.returnKey()) { + // We don't need a projection stage if returnKey was requested since the intended behavior + // is that the projection is ignored when returnKey is specified. + solnRoot = std::make_unique<ReturnKeyNode>( + addSortKeyGeneratorStageIfNeeded(query, hasSortStage, std::move(solnRoot)), + QueryPlannerCommon::extractSortKeyMetaFieldsFromProjection(qr.getProj())); + } else if (query.getProj()) { solnRoot = analyzeProjection(query, std::move(solnRoot), hasSortStage); // If we don't have a covered project, and we're not allowed to put an uncovered one in, // bail out. diff --git a/src/mongo/db/query/query_planner_common.cpp b/src/mongo/db/query/query_planner_common.cpp index 35cb1ac0293..2b2117676a8 100644 --- a/src/mongo/db/query/query_planner_common.cpp +++ b/src/mongo/db/query/query_planner_common.cpp @@ -75,4 +75,32 @@ void QueryPlannerCommon::reverseScans(QuerySolutionNode* node) { } } +// TODO SERVER-42422: reimplement by walking a projection AST rather than raw bson. +std::vector<std::string> QueryPlannerCommon::extractSortKeyMetaFieldsFromProjection( + const BSONObj& proj) { + std::vector<std::string> sortKeyMetaFields; + + for (auto&& elem : proj) { + if (elem.type() == BSONType::Object) { + BSONObj obj = elem.embeddedObject(); + // The caller must have already validated the projection, so we expect + // to see only one element. + invariant(1 == obj.nFields()); + + BSONElement firstSubElem = obj.firstElement(); + if (firstSubElem.fieldNameStringData() == "$meta") { + invariant(firstSubElem.type() == BSONType::String); + if (firstSubElem.valueStringData() == QueryRequest::metaSortKey) { + invariant(std::find(sortKeyMetaFields.begin(), + sortKeyMetaFields.end(), + elem.fieldName()) == sortKeyMetaFields.end()); + sortKeyMetaFields.push_back(elem.fieldName()); + } + } + } + } + + return sortKeyMetaFields; +} + } // namespace mongo diff --git a/src/mongo/db/query/query_planner_common.h b/src/mongo/db/query/query_planner_common.h index c232291e0e3..76c70742a3c 100644 --- a/src/mongo/db/query/query_planner_common.h +++ b/src/mongo/db/query/query_planner_common.h @@ -82,6 +82,14 @@ public: * the scan direction and index bounds. */ static void reverseScans(QuerySolutionNode* node); + + /** + * Extracts all field names for the sortKey meta-projection and stores them in the returned + * array. Returns an empty array if there were no sortKey meta-projection specified in the + * given projection 'proj'. For example, given a projection {a:1, b: {$meta: "sortKey"}, + * c: {$meta: "sortKey"}}, the returned vector will contain two elements ["b", "c"]. + */ + static std::vector<std::string> extractSortKeyMetaFieldsFromProjection(const BSONObj& proj); }; } // namespace mongo diff --git a/src/mongo/db/query/query_request.cpp b/src/mongo/db/query/query_request.cpp index b3c87b40ab8..fde6441559b 100644 --- a/src/mongo/db/query/query_request.cpp +++ b/src/mongo/db/query/query_request.cpp @@ -58,7 +58,6 @@ const char QueryRequest::queryOptionMaxTimeMS[] = "$maxTimeMS"; const string QueryRequest::metaGeoNearDistance("geoNearDistance"); const string QueryRequest::metaGeoNearPoint("geoNearPoint"); -const string QueryRequest::metaIndexKey("indexKey"); const string QueryRequest::metaRecordId("recordId"); const string QueryRequest::metaSortKey("sortKey"); const string QueryRequest::metaTextScore("textScore"); @@ -592,16 +591,6 @@ void QueryRequest::asFindCommandInternal(BSONObjBuilder* cmdBuilder) const { } } -void QueryRequest::addReturnKeyMetaProj() { - BSONObjBuilder projBob; - projBob.appendElements(_proj); - // We use $$ because it's never going to show up in a user's projection. - // The exact text doesn't matter. - BSONObj indexKey = BSON("$$" << BSON("$meta" << QueryRequest::metaIndexKey)); - projBob.append(indexKey.firstElement()); - _proj = projBob.obj(); -} - void QueryRequest::addShowRecordIdMetaProj() { BSONObjBuilder projBob; projBob.appendElements(_proj); @@ -937,7 +926,6 @@ Status QueryRequest::initFullQuery(const BSONObj& top) { // Won't throw. if (e.trueValue()) { _returnKey = true; - addReturnKeyMetaProj(); } } else if (name == "showDiskLoc") { // Won't throw. @@ -1004,11 +992,6 @@ void QueryRequest::initFromInt(int options) { } void QueryRequest::addMetaProjection() { - // We might need to update the projection object with a $meta projection. - if (returnKey()) { - addReturnKeyMetaProj(); - } - if (showRecordId()) { addShowRecordIdMetaProj(); } diff --git a/src/mongo/db/query/query_request.h b/src/mongo/db/query/query_request.h index 7b0cb74dd80..17e915ace5c 100644 --- a/src/mongo/db/query/query_request.h +++ b/src/mongo/db/query/query_request.h @@ -135,7 +135,6 @@ public: // Names of the $meta projection values. static const std::string metaGeoNearDistance; static const std::string metaGeoNearPoint; - static const std::string metaIndexKey; static const std::string metaRecordId; static const std::string metaSortKey; static const std::string metaTextScore; @@ -455,11 +454,6 @@ private: Status initFullQuery(const BSONObj& top); /** - * Updates the projection object with a $meta projection for the returnKey option. - */ - void addReturnKeyMetaProj(); - - /** * Updates the projection object with a $meta projection for the showRecordId option. */ void addShowRecordIdMetaProj(); diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index ddbe87074a8..6b076d80e96 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -31,6 +31,8 @@ #include "mongo/db/query/query_solution.h" +#include <boost/algorithm/string/join.hpp> + #include "mongo/bson/bsontypes.h" #include "mongo/bson/mutable/document.h" #include "mongo/bson/simple_bsonelement_comparator.h" @@ -848,6 +850,27 @@ bool IndexScanNode::operator==(const IndexScanNode& other) const { } // +// ReturnKeyNode +// + +void ReturnKeyNode::appendToString(str::stream* ss, int indent) const { + addIndent(ss, indent); + *ss << "RETURN_KEY\n"; + addIndent(ss, indent + 1); + *ss << "sortKeyMetaFields = [" << boost::algorithm::join(sortKeyMetaFields, ", ") << "]\n"; + addCommon(ss, indent); + addIndent(ss, indent + 1); + *ss << "Child:" << '\n'; + children[0]->appendToString(ss, indent + 2); +} + +QuerySolutionNode* ReturnKeyNode::clone() const { + auto copy = std::make_unique<ReturnKeyNode>( + std::unique_ptr<QuerySolutionNode>(children[0]->clone()), std::vector(sortKeyMetaFields)); + return copy.release(); +} + +// // ProjectionNode // diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h index cba57fd1263..48a0288a317 100644 --- a/src/mongo/db/query/query_solution.h +++ b/src/mongo/db/query/query_solution.h @@ -540,6 +540,35 @@ struct IndexScanNode : public QuerySolutionNode { std::set<StringData> multikeyFields; }; +struct ReturnKeyNode : public QuerySolutionNode { + ReturnKeyNode(std::unique_ptr<QuerySolutionNode> child, + std::vector<std::string> sortKeyMetaFields) + : QuerySolutionNode(std::move(child)), sortKeyMetaFields(std::move(sortKeyMetaFields)) {} + + StageType getType() const final { + return STAGE_RETURN_KEY; + } + + void appendToString(str::stream* ss, int indent) const final; + + bool fetched() const final { + return children[0]->fetched(); + } + bool hasField(const std::string& field) const final { + return false; + } + bool sortedByDiskLoc() const final { + return children[0]->sortedByDiskLoc(); + } + const BSONObjSet& getSort() const final { + return children[0]->getSort(); + } + + QuerySolutionNode* clone() const final; + + std::vector<std::string> sortKeyMetaFields; +}; + /** * We have a few implementations of the projection functionality. They are chosen by constructing * a type derived from this abstract struct. The most general implementation 'ProjectionNodeDefault' diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index 48cc9e39027..c8a92a75963 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -52,6 +52,7 @@ #include "mongo/db/exec/merge_sort.h" #include "mongo/db/exec/or.h" #include "mongo/db/exec/projection.h" +#include "mongo/db/exec/return_key.h" #include "mongo/db/exec/shard_filter.h" #include "mongo/db/exec/skip.h" #include "mongo/db/exec/sort.h" @@ -133,6 +134,13 @@ std::unique_ptr<PlanStage> buildStages(OperationContext* opCtx, return std::make_unique<SortKeyGeneratorStage>( cq.getExpCtx(), std::move(childStage), ws, keyGenNode->sortSpec); } + case STAGE_RETURN_KEY: { + auto returnKeyNode = static_cast<const ReturnKeyNode*>(root); + auto childStage = + buildStages(opCtx, collection, cq, qsol, returnKeyNode->children[0], ws); + return std::make_unique<ReturnKeyStage>( + opCtx, std::move(returnKeyNode->sortKeyMetaFields), ws, std::move(childStage)); + } case STAGE_PROJECTION_DEFAULT: { auto pn = static_cast<const ProjectionNodeDefault*>(root); auto childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws); diff --git a/src/mongo/db/query/stage_types.h b/src/mongo/db/query/stage_types.h index 8b34d4dfecc..17a67f1169f 100644 --- a/src/mongo/db/query/stage_types.h +++ b/src/mongo/db/query/stage_types.h @@ -87,6 +87,7 @@ enum StageType { STAGE_QUEUED_DATA, STAGE_RECORD_STORE_FAST_COUNT, + STAGE_RETURN_KEY, STAGE_SHARDING_FILTER, STAGE_SKIP, STAGE_SORT, |