summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/canonical_query.cpp5
-rw-r--r--src/mongo/db/query/canonical_query.h17
-rw-r--r--src/mongo/db/query/find.cpp34
-rw-r--r--src/mongo/db/query/get_executor.cpp8
-rw-r--r--src/mongo/db/query/plan_executor.h20
-rw-r--r--src/mongo/db/query/plan_executor_impl.cpp79
-rw-r--r--src/mongo/db/query/plan_executor_impl.h14
-rw-r--r--src/mongo/db/query/planner_access.cpp13
-rw-r--r--src/mongo/db/query/planner_analysis.cpp5
-rw-r--r--src/mongo/db/query/projection.cpp8
-rw-r--r--src/mongo/db/query/projection.h31
-rw-r--r--src/mongo/db/query/projection_test.cpp44
-rw-r--r--src/mongo/db/query/stage_builder.cpp2
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: {