diff options
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.h | 17 | ||||
-rw-r--r-- | src/mongo/db/query/find.cpp | 34 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/plan_executor.h | 20 | ||||
-rw-r--r-- | src/mongo/db/query/plan_executor_impl.cpp | 79 | ||||
-rw-r--r-- | src/mongo/db/query/plan_executor_impl.h | 14 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/query/planner_analysis.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/projection.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/projection.h | 31 | ||||
-rw-r--r-- | src/mongo/db/query/projection_test.cpp | 44 | ||||
-rw-r--r-- | src/mongo/db/query/stage_builder.cpp | 2 |
13 files changed, 172 insertions, 108 deletions
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index d3590e9c722..fdf06b7c849 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -271,9 +271,12 @@ Status CanonicalQuery::init(OperationContext* opCtx, if (!newParserStatus.isOK()) { return newParserStatus; } + + _metadataDeps = _proj->metadataDeps(); } - if (_proj && _proj->wantSortKey() && _qr->getSort().isEmpty()) { + if (_proj && _proj->metadataDeps()[DocumentMetadataFields::kSortKey] && + _qr->getSort().isEmpty()) { return Status(ErrorCodes::BadValue, "cannot use sortKey $meta projection without a sort"); } diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h index 3cacc3b4c9d..39ed4e927f5 100644 --- a/src/mongo/db/query/canonical_query.h +++ b/src/mongo/db/query/canonical_query.h @@ -133,6 +133,20 @@ public: } /** + * Returns a bitset indicating what metadata has been requested in the query. + */ + const QueryMetadataBitSet& metadataDeps() const { + return _metadataDeps; + } + + /** + * Allows callers to request metadata in addition to that needed as part of the query. + */ + void requestAdditionalMetadata(const QueryMetadataBitSet& additionalDeps) { + _metadataDeps |= additionalDeps; + } + + /** * Compute the "shape" of this query by encoding the match, projection and sort, and stripping * out the appropriate values. */ @@ -210,6 +224,9 @@ private: boost::optional<projection_ast::Projection> _proj; + // Keeps track of what metadata has been explicitly requested. + QueryMetadataBitSet _metadataDeps; + std::unique_ptr<CollatorInterface> _collator; bool _canHaveNoopMatchNodes = false; diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp index bf2d41c52a8..12b5f4073ee 100644 --- a/src/mongo/db/query/find.cpp +++ b/src/mongo/db/query/find.cpp @@ -181,9 +181,11 @@ void generateBatch(int ntoreturn, PlanExecutor::ExecState* state) { PlanExecutor* exec = cursor->getExecutor(); - BSONObj obj; + Document doc; while (!FindCommon::enoughForGetMore(ntoreturn, *numResults) && - PlanExecutor::ADVANCED == (*state = exec->getNext(&obj, nullptr))) { + PlanExecutor::ADVANCED == (*state = exec->getNext(&doc, nullptr))) { + BSONObj obj = doc.toBson(); + // If we can't fit this result inside the current batch, then we stash it for later. if (!FindCommon::haveSpaceForNext(obj, *numResults, bb->len())) { exec->enqueue(obj); @@ -204,7 +206,7 @@ void generateBatch(int ntoreturn, error() << "getMore executor error, stats: " << redact(Explain::getWinningPlanStats(exec)); // We should always have a valid status object by this point. - auto status = WorkingSetCommon::getMemberObjectStatus(obj); + auto status = WorkingSetCommon::getMemberObjectStatus(doc); invariant(!status.isOK()); uassertStatusOK(status); } @@ -684,7 +686,10 @@ std::string runQuery(OperationContext* opCtx, curOp.setPlanSummary_inlock(Explain::getPlanSummary(exec.get())); } - while (PlanExecutor::ADVANCED == (state = exec->getNext(&obj, nullptr))) { + Document doc; + while (PlanExecutor::ADVANCED == (state = exec->getNext(&doc, nullptr))) { + obj = doc.toBson(); + // If we can't fit this result inside the current batch, then we stash it for later. if (!FindCommon::haveSpaceForNext(obj, numResults, bb.len())) { exec->enqueue(obj); @@ -709,7 +714,7 @@ std::string runQuery(OperationContext* opCtx, if (PlanExecutor::FAILURE == state) { error() << "Plan executor error during find: " << PlanExecutor::statestr(state) << ", stats: " << redact(Explain::getWinningPlanStats(exec.get())); - uassertStatusOKWithContext(WorkingSetCommon::getMemberObjectStatus(obj), + uassertStatusOKWithContext(WorkingSetCommon::getMemberObjectStatus(doc), "Executor error during OP_QUERY find"); MONGO_UNREACHABLE; } @@ -727,14 +732,17 @@ std::string runQuery(OperationContext* opCtx, // Allocate a new ClientCursor and register it with the cursor manager. ClientCursorPin pinnedCursor = CursorManager::get(opCtx)->registerCursor( opCtx, - {std::move(exec), - nss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), - opCtx->getWriteConcern(), - readConcernArgs, - upconvertedQuery, - ClientCursorParams::LockPolicy::kLockExternally, - {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)}}); + { + std::move(exec), + nss, + AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), + opCtx->getWriteConcern(), + readConcernArgs, + upconvertedQuery, + ClientCursorParams::LockPolicy::kLockExternally, + {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)}, + false // needsMerge always 'false' for find(). + }); ccId = pinnedCursor.getCursor()->cursorid(); LOG(5) << "caching executor with cursorid " << ccId << " after returning " << numResults diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 2a2a3559040..29cc73c097a 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -390,8 +390,8 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, std::move(root)); } - // Add a SortKeyGeneratorStage if there is a $meta sortKey projection. - if (canonicalQuery->getProj() && canonicalQuery->getProj()->wantSortKey()) { + // Add a SortKeyGeneratorStage if the query requested sortKey metadata. + if (canonicalQuery->metadataDeps()[DocumentMetadataFields::kSortKey]) { root = std::make_unique<SortKeyGeneratorStage>( canonicalQuery->getExpCtx(), std::move(root), @@ -675,8 +675,10 @@ StatusWith<unique_ptr<PlanStage>> applyProjection(OperationContext* opCtx, "cannot use a positional projection and return the new document"}; } + cq->requestAdditionalMetadata(proj.metadataDeps()); + // $meta sortKey is not allowed to be projected in findAndModify commands. - if (proj.wantSortKey()) { + if (cq->metadataDeps()[DocumentMetadataFields::kSortKey]) { return {ErrorCodes::BadValue, "Cannot use a $meta sortKey projection in findAndModify commands."}; } diff --git a/src/mongo/db/query/plan_executor.h b/src/mongo/db/query/plan_executor.h index a0bf2344a6b..178652b2594 100644 --- a/src/mongo/db/query/plan_executor.h +++ b/src/mongo/db/query/plan_executor.h @@ -289,6 +289,11 @@ public: */ virtual OperationContext* getOpCtx() const = 0; + /** + * Return the ExpressionContext that the plan is currently executing with. + */ + virtual const boost::intrusive_ptr<ExpressionContext>& getExpCtx() const = 0; + // // Methods that just pass down to the PlanStage tree. // @@ -352,10 +357,19 @@ public: * For write operations, the return depends on the particulars of the write stage. * * If a YIELD_AUTO policy is set, then this method may yield. + * + * The Documents returned by this method may not be owned. If the caller wants to ensure a + * returned Document is preserved across a yield, getOwned() should be called. */ + virtual ExecState getNextSnapshotted(Snapshotted<Document>* objOut, RecordId* dlOut) = 0; virtual ExecState getNextSnapshotted(Snapshotted<BSONObj>* objOut, RecordId* dlOut) = 0; - virtual ExecState getNext(BSONObj* objOut, RecordId* dlOut) = 0; + virtual ExecState getNext(Document* objOut, RecordId* dlOut) = 0; + + /** + * Will perform the Document -> BSON conversion for the caller. + */ + virtual ExecState getNext(BSONObj* out, RecordId* dlOut) = 0; /** * Returns 'true' if the plan is done producing results (or writing), 'false' otherwise. @@ -421,6 +435,7 @@ public: * If used in combination with getNextSnapshotted(), then the SnapshotId associated with * 'obj' will be null when 'obj' is dequeued. */ + virtual void enqueue(const Document& obj) = 0; virtual void enqueue(const BSONObj& obj) = 0; virtual bool isMarkedAsKilled() const = 0; @@ -442,8 +457,9 @@ public: virtual BSONObj getPostBatchResumeToken() const = 0; /** - * Turns a BSONObj representing an error status produced by getNext() into a Status. + * Turns a Document representing an error status produced by getNext() into a Status. */ + virtual Status getMemberObjectStatus(const Document& memberObj) const = 0; virtual Status getMemberObjectStatus(const BSONObj& memberObj) const = 0; }; diff --git a/src/mongo/db/query/plan_executor_impl.cpp b/src/mongo/db/query/plan_executor_impl.cpp index 3d0629d5d30..46807db4359 100644 --- a/src/mongo/db/query/plan_executor_impl.cpp +++ b/src/mongo/db/query/plan_executor_impl.cpp @@ -324,6 +324,10 @@ OperationContext* PlanExecutorImpl::getOpCtx() const { return _opCtx; } +const boost::intrusive_ptr<ExpressionContext>& PlanExecutorImpl::getExpCtx() const { + return _expCtx; +} + void PlanExecutorImpl::saveState() { invariant(_currentState == kUsable || _currentState == kSaved); @@ -377,7 +381,16 @@ void PlanExecutorImpl::reattachToOperationContext(OperationContext* opCtx) { } PlanExecutor::ExecState PlanExecutorImpl::getNext(BSONObj* objOut, RecordId* dlOut) { - Snapshotted<BSONObj> snapshotted; + Document doc; + const auto state = getNext(&doc, dlOut); + if (objOut) { + *objOut = doc.toBson(); + } + return state; +} + +PlanExecutor::ExecState PlanExecutorImpl::getNext(Document* objOut, RecordId* dlOut) { + Snapshotted<Document> snapshotted; ExecState state = _getNextImpl(objOut ? &snapshotted : nullptr, dlOut); if (objOut) { @@ -387,13 +400,25 @@ PlanExecutor::ExecState PlanExecutorImpl::getNext(BSONObj* objOut, RecordId* dlO return state; } -PlanExecutor::ExecState PlanExecutorImpl::getNextSnapshotted(Snapshotted<BSONObj>* objOut, +PlanExecutor::ExecState PlanExecutorImpl::getNextSnapshotted(Snapshotted<Document>* objOut, RecordId* dlOut) { // Detaching from the OperationContext means that the returned snapshot ids could be invalid. invariant(!_everDetachedFromOperationContext); return _getNextImpl(objOut, dlOut); } +PlanExecutor::ExecState PlanExecutorImpl::getNextSnapshotted(Snapshotted<BSONObj>* objOut, + RecordId* dlOut) { + // Detaching from the OperationContext means that the returned snapshot ids could be invalid. + invariant(!_everDetachedFromOperationContext); + Snapshotted<Document> docOut; + const auto status = _getNextImpl(&docOut, dlOut); + if (objOut) { + *objOut = {docOut.snapshotId(), docOut.value().toBson()}; + } + return status; +} + bool PlanExecutorImpl::_shouldListenForInserts() { return _cq && _cq->getQueryRequest().isTailableAndAwaitData() && awaitDataState(_opCtx).shouldWaitForInserts && _opCtx->checkForInterruptNoAssert().isOK() && @@ -438,7 +463,7 @@ std::shared_ptr<CappedInsertNotifier> PlanExecutorImpl::_getCappedInsertNotifier } PlanExecutor::ExecState PlanExecutorImpl::_waitForInserts(CappedInsertNotifierData* notifierData, - Snapshotted<BSONObj>* errorObj) { + Snapshotted<Document>* errorObj) { invariant(notifierData->notifier); // The notifier wait() method will not wait unless the version passed to it matches the @@ -463,19 +488,19 @@ PlanExecutor::ExecState PlanExecutorImpl::_waitForInserts(CappedInsertNotifierDa } if (errorObj) { - *errorObj = Snapshotted<BSONObj>(SnapshotId(), - WorkingSetCommon::buildMemberStatusObject(yieldResult)); + *errorObj = Snapshotted<Document>(SnapshotId(), + WorkingSetCommon::buildMemberStatusObject(yieldResult)); } return FAILURE; } -PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* objOut, +PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<Document>* objOut, RecordId* dlOut) { if (MONGO_unlikely(planExecutorAlwaysFails.shouldFail())) { Status status(ErrorCodes::InternalError, str::stream() << "PlanExecutor hit planExecutorAlwaysFails fail point"); *objOut = - Snapshotted<BSONObj>(SnapshotId(), WorkingSetCommon::buildMemberStatusObject(status)); + Snapshotted<Document>(SnapshotId(), WorkingSetCommon::buildMemberStatusObject(status)); return PlanExecutor::FAILURE; } @@ -483,8 +508,8 @@ PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* obj invariant(_currentState == kUsable); if (isMarkedAsKilled()) { if (nullptr != objOut) { - *objOut = Snapshotted<BSONObj>(SnapshotId(), - WorkingSetCommon::buildMemberStatusObject(_killStatus)); + *objOut = Snapshotted<Document>(SnapshotId(), + WorkingSetCommon::buildMemberStatusObject(_killStatus)); } return PlanExecutor::FAILURE; } @@ -517,7 +542,7 @@ PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* obj auto yieldStatus = _yieldPolicy->yieldOrInterrupt(); if (!yieldStatus.isOK()) { if (objOut) { - *objOut = Snapshotted<BSONObj>( + *objOut = Snapshotted<Document>( SnapshotId(), WorkingSetCommon::buildMemberStatusObject(yieldStatus)); } return PlanExecutor::FAILURE; @@ -542,15 +567,11 @@ PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* obj } else { // TODO: currently snapshot ids are only associated with documents, and // not with index keys. - *objOut = Snapshotted<BSONObj>(SnapshotId(), member->keyData[0].keyData); + *objOut = Snapshotted<Document>(SnapshotId(), + Document{member->keyData[0].keyData}); } } else if (member->hasObj()) { - *objOut = Snapshotted<BSONObj>( - member->doc.snapshotId(), - member->metadata() && member->doc.value().metadata() - ? member->doc.value().toBsonWithMetaData( - _expCtx ? _expCtx->use42ChangeStreamSortKeys : false) - : member->doc.value().toBson()); + *objOut = member->doc; } else { _workingSet->free(id); hasRequestedData = false; @@ -567,6 +588,12 @@ PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* obj } if (hasRequestedData) { + // transfer the metadata from the WSM to Document. + invariant(objOut); + MutableDocument md(std::move(objOut->value())); + md.setMetadata(member->releaseMetadata()); + objOut->setValue(md.freeze()); + _workingSet->free(id); return PlanExecutor::ADVANCED; } @@ -609,9 +636,8 @@ PlanExecutor::ExecState PlanExecutorImpl::_getNextImpl(Snapshotted<BSONObj>* obj if (nullptr != objOut) { invariant(WorkingSet::INVALID_ID != id); - BSONObj statusObj = - WorkingSetCommon::getStatusMemberDocument(*_workingSet, id)->toBson(); - *objOut = Snapshotted<BSONObj>(SnapshotId(), statusObj); + auto statusObj = WorkingSetCommon::getStatusMemberDocument(*_workingSet, id); + *objOut = Snapshotted<Document>(SnapshotId(), *statusObj); } return PlanExecutor::FAILURE; @@ -643,7 +669,7 @@ void PlanExecutorImpl::dispose(OperationContext* opCtx) { Status PlanExecutorImpl::executePlan() { invariant(_currentState == kUsable); - BSONObj obj; + Document obj; PlanExecutor::ExecState state = PlanExecutor::ADVANCED; while (PlanExecutor::ADVANCED == state) { state = this->getNext(&obj, nullptr); @@ -666,10 +692,14 @@ Status PlanExecutorImpl::executePlan() { } -void PlanExecutorImpl::enqueue(const BSONObj& obj) { +void PlanExecutorImpl::enqueue(const Document& obj) { _stash.push(obj.getOwned()); } +void PlanExecutorImpl::enqueue(const BSONObj& obj) { + enqueue(Document{obj}); +} + bool PlanExecutorImpl::isMarkedAsKilled() const { return !_killStatus.isOK(); } @@ -701,8 +731,11 @@ BSONObj PlanExecutorImpl::getPostBatchResumeToken() const { return {}; } -Status PlanExecutorImpl::getMemberObjectStatus(const BSONObj& memberObj) const { +Status PlanExecutorImpl::getMemberObjectStatus(const Document& memberObj) const { return WorkingSetCommon::getMemberObjectStatus(memberObj); } +Status PlanExecutorImpl::getMemberObjectStatus(const BSONObj& memberObj) const { + return WorkingSetCommon::getMemberObjectStatus(memberObj); +} } // namespace mongo diff --git a/src/mongo/db/query/plan_executor_impl.h b/src/mongo/db/query/plan_executor_impl.h index 629f66c6474..8d796e33ca4 100644 --- a/src/mongo/db/query/plan_executor_impl.h +++ b/src/mongo/db/query/plan_executor_impl.h @@ -61,17 +61,21 @@ public: CanonicalQuery* getCanonicalQuery() const final; const NamespaceString& nss() const final; OperationContext* getOpCtx() const final; + const boost::intrusive_ptr<ExpressionContext>& getExpCtx() const final; void saveState() final; void restoreState() final; void detachFromOperationContext() final; void reattachToOperationContext(OperationContext* opCtx) final; void restoreStateWithoutRetrying() final; + ExecState getNextSnapshotted(Snapshotted<Document>* objOut, RecordId* dlOut) final; ExecState getNextSnapshotted(Snapshotted<BSONObj>* objOut, RecordId* dlOut) final; - ExecState getNext(BSONObj* objOut, RecordId* dlOut) final; + ExecState getNext(Document* objOut, RecordId* dlOut) final; + ExecState getNext(BSONObj* out, RecordId* dlOut) final; bool isEOF() final; Status executePlan() final; void markAsKilled(Status killStatus) final; void dispose(OperationContext* opCtx) final; + void enqueue(const Document& obj) final; void enqueue(const BSONObj& obj) final; bool isMarkedAsKilled() const final; Status getKillStatus() final; @@ -79,6 +83,8 @@ public: bool isDetached() const final; Timestamp getLatestOplogTimestamp() const final; BSONObj getPostBatchResumeToken() const final; + + Status getMemberObjectStatus(const Document& memberObj) const final; Status getMemberObjectStatus(const BSONObj& memberObj) const final; private: @@ -142,12 +148,12 @@ private: * describing the error. */ ExecState _waitForInserts(CappedInsertNotifierData* notifierData, - Snapshotted<BSONObj>* errorObj); + Snapshotted<Document>* errorObj); /** * Common implementation for getNext() and getNextSnapshotted(). */ - ExecState _getNextImpl(Snapshotted<BSONObj>* objOut, RecordId* dlOut); + ExecState _getNextImpl(Snapshotted<Document>* objOut, RecordId* dlOut); // The OperationContext that we're executing within. This can be updated if necessary by using // detachFromOperationContext() and reattachToOperationContext(). @@ -181,7 +187,7 @@ private: // A stash of results generated by this plan that the user of the PlanExecutor didn't want // to consume yet. We empty the queue before retrieving further results from the plan // stages. - std::queue<BSONObj> _stash; + std::queue<Document> _stash; enum { kUsable, kSaved, kDetached, kDisposed } _currentState = kUsable; diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp index eb837ff2121..ace8c86c703 100644 --- a/src/mongo/db/query/planner_access.cpp +++ b/src/mongo/db/query/planner_access.cpp @@ -285,20 +285,17 @@ std::unique_ptr<QuerySolutionNode> QueryPlannerAccess::makeLeafNode( auto ret = std::make_unique<GeoNear2DNode>(index); ret->nq = &nearExpr->getData(); ret->baseBounds.fields.resize(index.keyPattern.nFields()); - if (nullptr != query.getProj()) { - ret->addPointMeta = query.getProj()->wantGeoNearPoint(); - ret->addDistMeta = query.getProj()->wantGeoNearDistance(); - } + ret->addPointMeta = query.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]; + ret->addDistMeta = query.metadataDeps()[DocumentMetadataFields::kGeoNearDist]; return std::move(ret); } else { auto ret = std::make_unique<GeoNear2DSphereNode>(index); ret->nq = &nearExpr->getData(); ret->baseBounds.fields.resize(index.keyPattern.nFields()); - if (nullptr != query.getProj()) { - ret->addPointMeta = query.getProj()->wantGeoNearPoint(); - ret->addDistMeta = query.getProj()->wantGeoNearDistance(); - } + ret->addPointMeta = query.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]; + ret->addDistMeta = query.metadataDeps()[DocumentMetadataFields::kGeoNearDist]; + return std::move(ret); } } else if (MatchExpression::TEXT == expr->matchType()) { diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp index 3646422184f..2e6ae124505 100644 --- a/src/mongo/db/query/planner_analysis.cpp +++ b/src/mongo/db/query/planner_analysis.cpp @@ -328,7 +328,7 @@ auto produceCoveredKeyObj(QuerySolutionNode* solnRoot) { */ std::unique_ptr<QuerySolutionNode> addSortKeyGeneratorStageIfNeeded( const CanonicalQuery& query, bool hasSortStage, std::unique_ptr<QuerySolutionNode> solnRoot) { - if (!hasSortStage && query.getProj() && query.getProj()->wantSortKey()) { + if (!hasSortStage && query.metadataDeps()[DocumentMetadataFields::kSortKey]) { auto keyGenNode = std::make_unique<SortKeyGeneratorNode>(); keyGenNode->sortSpec = query.getQueryRequest().getSort(); keyGenNode->children.push_back(solnRoot.release()); @@ -803,6 +803,9 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess( if (solnRoot->fetched() && params.options & QueryPlannerParams::NO_UNCOVERED_PROJECTIONS) return nullptr; } else { + // Even if there's no projection, the client may want sort key metadata. + solnRoot = addSortKeyGeneratorStageIfNeeded(query, hasSortStage, std::move(solnRoot)); + // If there's no projection, we must fetch, as the user wants the entire doc. if (!solnRoot->fetched() && !(params.options & QueryPlannerParams::IS_COUNT)) { FetchNode* fetch = new FetchNode(); diff --git a/src/mongo/db/query/projection.cpp b/src/mongo/db/query/projection.cpp index 6ad92a8859f..d2a4ee89264 100644 --- a/src/mongo/db/query/projection.cpp +++ b/src/mongo/db/query/projection.cpp @@ -43,7 +43,7 @@ namespace { * context. */ struct DepsAnalysisData { - DepsTracker fieldDependencyTracker{DepsTracker::kAllMetadataAvailable}; + DepsTracker fieldDependencyTracker{DepsTracker::kAllMetadata}; void addRequiredField(const std::string& fieldName) { fieldDependencyTracker.fields.insert(fieldName); @@ -188,11 +188,7 @@ auto analyzeProjection(ProjectionPathASTNode* root, ProjectType type) { deps.requiresDocument = true; } - deps.needsTextScore = tracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE); - deps.needsGeoPoint = tracker.getNeedsMetadata(DepsTracker::MetadataType::GEO_NEAR_POINT); - deps.needsGeoDistance = tracker.getNeedsMetadata(DepsTracker::MetadataType::GEO_NEAR_DISTANCE); - deps.needsSortKey = tracker.getNeedsMetadata(DepsTracker::MetadataType::SORT_KEY); - + deps.metadataRequested = tracker.metadataDeps(); return deps; } } // namespace diff --git a/src/mongo/db/query/projection.h b/src/mongo/db/query/projection.h index 8067b08591d..91afe2b931f 100644 --- a/src/mongo/db/query/projection.h +++ b/src/mongo/db/query/projection.h @@ -29,6 +29,7 @@ #pragma once +#include "mongo/db/exec/document_value/document_metadata_fields.h" #include "mongo/db/query/projection_ast.h" #include "mongo/util/str.h" @@ -47,12 +48,9 @@ struct ProjectionDependencies { // Which fields are necessary to perform the projection, or boost::none if all are required. boost::optional<std::vector<std::string>> requiredFields; - bool needsGeoDistance = false; - bool needsGeoPoint = false; - bool needsSortKey = false; - bool needsTextScore = false; - bool hasDottedPath = false; + + QueryMetadataBitSet metadataRequested; }; /** @@ -95,23 +93,8 @@ public: return *_deps.requiredFields; } - /** - * Does the projection want geoNear metadata? If so any geoNear stage should include them. - */ - bool wantGeoNearDistance() const { - return _deps.needsGeoDistance; - } - - bool wantGeoNearPoint() const { - return _deps.needsGeoPoint; - } - - bool wantSortKey() const { - return _deps.needsSortKey; - } - - bool wantTextScore() const { - return _deps.needsTextScore; + const QueryMetadataBitSet& metadataDeps() const { + return _deps.metadataRequested; } /** @@ -135,8 +118,8 @@ public: * on top-level fields, has no positional projection, and doesn't require the sort key. */ bool isSimple() const { - return !_deps.hasDottedPath && !_deps.requiresMatchDetails && !_deps.needsSortKey && - !_deps.requiresDocument; + return !_deps.hasDottedPath && !_deps.requiresMatchDetails && + !_deps.metadataRequested.any() && !_deps.requiresDocument; } private: diff --git a/src/mongo/db/query/projection_test.cpp b/src/mongo/db/query/projection_test.cpp index b11b00bd270..c0044173cd4 100644 --- a/src/mongo/db/query/projection_test.cpp +++ b/src/mongo/db/query/projection_test.cpp @@ -217,12 +217,12 @@ TEST(QueryProjectionTest, InvalidPositionalProjectionDefaultPathMatchExpression) TEST(QueryProjectionTest, ProjectionDefaults) { auto proj = createProjection("{}", "{}"); - ASSERT_FALSE(proj.wantSortKey()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_TRUE(proj.requiresDocument()); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); - ASSERT_FALSE(proj.wantTextScore()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kTextScore]); } TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjection) { @@ -230,11 +230,11 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjection) { auto proj = createProjection("{}", "{foo: {$meta: 'sortKey'}}"); ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{foo: {$meta: 'sortKey'}}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); ASSERT_TRUE(proj.requiresDocument()); } @@ -242,11 +242,11 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjectionWithOtherFie auto proj = createProjection("{}", "{a: 0, foo: {$meta: 'sortKey'}}"); ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 0, foo: {$meta: 'sortKey'}}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); ASSERT_TRUE(proj.requiresDocument()); } @@ -254,11 +254,11 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInInclusionProjection) { auto proj = createProjection("{}", "{a: 1, foo: {$meta: 'sortKey'}}"); ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 1, foo: {$meta: 'sortKey'}}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); ASSERT_FALSE(proj.requiresDocument()); } @@ -266,12 +266,12 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionDoesNotRequireDocument) { auto proj = createProjection("{}", "{a: 1, foo: {$meta: 'sortKey'}, _id: 0}"); ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 1, foo: {$meta: 'sortKey'}, _id: 0}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_FALSE(proj.requiresDocument()); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); } TEST(QueryProjectionTest, SortKeyMetaAndSlice) { @@ -279,12 +279,12 @@ TEST(QueryProjectionTest, SortKeyMetaAndSlice) { ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$slice: 1}}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_TRUE(proj.requiresDocument()); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); } TEST(QueryProjectionTest, SortKeyMetaAndElemMatch) { @@ -293,12 +293,12 @@ TEST(QueryProjectionTest, SortKeyMetaAndElemMatch) { ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$elemMatch: {a: 1}}}")); - ASSERT_TRUE(proj.wantSortKey()); + ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_TRUE(proj.requiresDocument()); ASSERT_FALSE(proj.requiresMatchDetails()); - ASSERT_FALSE(proj.wantGeoNearDistance()); - ASSERT_FALSE(proj.wantGeoNearPoint()); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); + ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); } // diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index 0beb57d2024..b70ca49a5a3 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -265,7 +265,7 @@ std::unique_ptr<PlanStage> buildStages(OperationContext* opCtx, // practice, this means that it is illegal to use the StageBuilder on a QuerySolution // created by planning a query that contains "no-op" expressions. params.query = static_cast<FTSQueryImpl&>(*node->ftsQuery); - params.wantTextScore = (cq.getProj() && cq.getProj()->wantTextScore()); + params.wantTextScore = cq.metadataDeps()[DocumentMetadataFields::kTextScore]; return std::make_unique<TextStage>(opCtx, params, ws, node->filter.get()); } case STAGE_SHARDING_FILTER: { |