diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp index f5ea2cf..45913b2 100644 --- a/src/mongo/db/exec/projection_exec.cpp +++ b/src/mongo/db/exec/projection_exec.cpp @@ -147,7 +147,11 @@ ProjectionExec::ProjectionExec(const BSONObj& spec, } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearDistance) { _meta[e.fieldName()] = META_GEONEAR_DIST; } else if (e2.valuestr() == LiteParsedQuery::metaIndexKey) { - _hasReturnKey = true; + if (e.fieldNameStringData() == "$returnKey") { + _hasReturnKey = true; + } else { + _meta[e.fieldName()] = META_IX_KEY; + } } else { // This shouldn't happen, should be caught by parsing. verify(0); @@ -288,11 +292,11 @@ Status ProjectionExec::transform(WorkingSetMember* member) const { continue; } - // $meta sortKey is the only meta-projection which is allowed to operate on index keys - // rather than the full document. + // $meta indexKey and sortKey are the only meta-projections which is allowed to operate + // on index keys rather than the full document. auto metaIt = _meta.find(specElt.fieldName()); if (metaIt != _meta.end()) { - invariant(metaIt->second == META_SORT_KEY); + invariant(metaIt->second == META_IX_KEY || metaIt->second == META_SORT_KEY); continue; } @@ -341,6 +345,12 @@ Status ProjectionExec::transform(WorkingSetMember* member) const { } else { bob.append(it->first, 0.0); } + } else if (META_IX_KEY == it->second) { + if (member->hasComputed(WSM_INDEX_KEY)) { + const IndexKeyComputedData* key = + static_cast(member->getComputed(WSM_INDEX_KEY)); + bob.append(it->first, key->getKey()); + } } else if (META_SORT_KEY == it->second) { auto sortKeyMetaStatus = addSortKeyMetaProj(it->first, *member, &bob); if (!sortKeyMetaStatus.isOK()) { diff --git a/src/mongo/db/query/lite_parsed_query.cpp b/src/mongo/db/query/lite_parsed_query.cpp index 672d143..997ed6d 100644 --- a/src/mongo/db/query/lite_parsed_query.cpp +++ b/src/mongo/db/query/lite_parsed_query.cpp @@ -592,7 +592,7 @@ void LiteParsedQuery::addReturnKeyMetaProj() { 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" << LiteParsedQuery::metaIndexKey)); + BSONObj indexKey = BSON("$returnKey" << BSON("$meta" << LiteParsedQuery::metaIndexKey)); projBob.append(indexKey.firstElement()); _proj = projBob.obj(); } diff --git a/src/mongo/db/query/parsed_projection.cpp b/src/mongo/db/query/parsed_projection.cpp index b977931..b93e6ac 100644 --- a/src/mongo/db/query/parsed_projection.cpp +++ b/src/mongo/db/query/parsed_projection.cpp @@ -57,7 +57,8 @@ Status ParsedProjection::make(const BSONObj& spec, bool includeID = true; - bool hasIndexKeyProjection = false; + bool hasReturnKeyProjection = false; + bool wantIndexKey = false; bool wantGeoNearPoint = false; bool wantGeoNearDistance = false; @@ -153,9 +154,13 @@ Status ParsedProjection::make(const BSONObj& spec, return Status(ErrorCodes::BadValue, "unsupported $meta operator: " + e2.str()); } - // This clobbers everything else. if (e2.valuestr() == LiteParsedQuery::metaIndexKey) { - hasIndexKeyProjection = true; + if (e.fieldNameStringData() == "$returnKey") { + // $returnKey clobbers everything else. + hasReturnKeyProjection = true; + } else { + wantIndexKey = true; + } } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearDistance) { wantGeoNearDistance = true; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearPoint) { @@ -164,8 +169,9 @@ Status ParsedProjection::make(const BSONObj& spec, wantSortKey = true; } - // Of the $meta projections, only sortKey can be covered. - if (e2.valuestr() != LiteParsedQuery::metaSortKey) { + // Of the $meta projections, only indexKey and sortKey can be covered. + if (e2.valuestr() != LiteParsedQuery::metaIndexKey && + e2.valuestr() != LiteParsedQuery::metaSortKey) { requiresDocument = true; } } else { @@ -246,7 +252,8 @@ Status ParsedProjection::make(const BSONObj& spec, // Save the raw spec. It should be owned by the LiteParsedQuery. verify(spec.isOwned()); pp->_source = spec; - pp->_returnKey = hasIndexKeyProjection; + pp->_returnKey = hasReturnKeyProjection; + pp->_wantIndexKey = wantIndexKey; pp->_requiresDocument = requiresDocument; // Add meta-projections. @@ -271,10 +278,16 @@ Status ParsedProjection::make(const BSONObj& spec, if (includeID && mongoutils::str::equals(elt.fieldName(), "_id")) { continue; } - // $meta sortKey should not be checked as a part of _requiredFields, since it can + + // $meta indexKey should not be checked as part of _requiredFields because it can always + // produce a covered projection. + // + // $meta sortKey should not be checked as a part of _requiredFields because it can // potentially produce a covered projection as long as the sort key is covered. if (BSONType::Object == elt.type()) { dassert(elt.Obj() == BSON("$meta" + << "indexKey") || + elt.Obj() == BSON("$meta" << "sortKey")); continue; } @@ -284,8 +297,8 @@ Status ParsedProjection::make(const BSONObj& spec, } } - // returnKey clobbers everything except for sortKey meta-projection. - if (hasIndexKeyProjection && !wantSortKey) { + // $returnKey clobbers everything except for sortKey meta-projection. + if (hasReturnKeyProjection && !wantSortKey) { pp->_requiresDocument = false; } diff --git a/src/mongo/db/query/parsed_projection.h b/src/mongo/db/query/parsed_projection.h index 3e7d97e..b6a27f8 100644 --- a/src/mongo/db/query/parsed_projection.h +++ b/src/mongo/db/query/parsed_projection.h @@ -91,10 +91,14 @@ public: return _wantGeoNearPoint; } - bool wantIndexKey() const { + bool hasReturnKey() const { return _returnKey; } + bool wantIndexKey() const { + return _wantIndexKey; + } + bool wantSortKey() const { return _wantSortKey; } @@ -137,6 +141,8 @@ private: bool _returnKey = false; + bool _wantIndexKey = false; + // Whether this projection includes a sortKey meta-projection. bool _wantSortKey = false; }; diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index 54bcc0b..7f449d4 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -208,6 +208,9 @@ QuerySolutionNode* QueryPlannerAccess::makeLeafNode( isn->bounds.fields.resize(index.keyPattern.nFields()); isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); + if (query.getProj() && query.getProj()->wantIndexKey()) { + isn->addKeyMetadata = true; + } // Get the ixtag->pos-th element of the index key pattern. // TODO: cache this instead/with ixtag->pos? @@ -1238,6 +1241,9 @@ QuerySolutionNode* QueryPlannerAccess::scanWholeIndex(const IndexEntry& index, isn->indexIsMultiKey = index.multikey; isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); + if (query.getProj() && query.getProj()->wantIndexKey()) { + isn->addKeyMetadata = true; + } IndexBoundsBuilder::allValuesBounds(index.keyPattern, &isn->bounds); @@ -1375,6 +1381,10 @@ QuerySolutionNode* QueryPlannerAccess::makeIndexScan(const IndexEntry& index, isn->direction = 1; isn->maxScan = query.getParsed().getMaxScan(); isn->addKeyMetadata = query.getParsed().returnKey(); + if (query.getProj() && query.getProj()->wantIndexKey()) { + isn->addKeyMetadata = true; + } + isn->bounds.isSimpleRange = true; isn->bounds.startKey = startKey; isn->bounds.endKey = endKey; diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index 7139b22..0b67e72 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -715,7 +715,7 @@ QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(const CanonicalQuery& que fetch->children.push_back(solnRoot); solnRoot = fetch; } - } else if (!query.getProj()->wantIndexKey()) { + } else if (!query.getProj()->hasReturnKey()) { // The only way we're here is if it's a simple projection. That is, we can pick out // the fields we want to include and they're not dotted. So we want to execute the // projection in the fast-path simple fashion. Just don't know which fast path yet. @@ -773,6 +773,13 @@ QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(const CanonicalQuery& que } } + // If we have a $meta indexKey, just use the project default path, as currently the + // project fast paths cannot handle $meta indexKey projections. + if (query.getProj()->wantIndexKey()) { + projType = ProjectionNode::DEFAULT; + LOG(5) << "PROJECTION: needs $meta indexKey, using DEFAULT path instead"; + } + // If we have a $meta sortKey, just use the project default path, as currently the // project fast paths cannot handle $meta sortKey projections. if (query.getProj()->wantSortKey()) {