summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorJacob Evans <jacob.evans@10gen.com>2018-11-20 15:51:46 -0500
committerJacob Evans <jacob.evans@10gen.com>2018-12-28 17:40:50 -0500
commit53d9bc9d3ebc9dee0d374faeb23bb12bbf16a1b8 (patch)
tree6e9293215afa71412ccdf83504a770f4eb7390bc /src/mongo/db
parent6734c12d17dd4c0e2738a47feb7114221d6ba66d (diff)
downloadmongo-53d9bc9d3ebc9dee0d374faeb23bb12bbf16a1b8.tar.gz
SERVER-37831 Refactor ProjectionStage/ProjectionExec
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/exec/plan_stage.h11
-rw-r--r--src/mongo/db/exec/projection.cpp319
-rw-r--r--src/mongo/db/exec/projection.h152
-rw-r--r--src/mongo/db/exec/projection_exec.cpp291
-rw-r--r--src/mongo/db/exec/projection_exec.h122
-rw-r--r--src/mongo/db/exec/projection_exec_test.cpp397
-rw-r--r--src/mongo/db/exec/working_set.cpp21
-rw-r--r--src/mongo/db/exec/working_set.h24
-rw-r--r--src/mongo/db/query/get_executor.cpp29
-rw-r--r--src/mongo/db/query/stage_builder.cpp35
10 files changed, 750 insertions, 651 deletions
diff --git a/src/mongo/db/exec/plan_stage.h b/src/mongo/db/exec/plan_stage.h
index cd3f1963962..7aa5f725f85 100644
--- a/src/mongo/db/exec/plan_stage.h
+++ b/src/mongo/db/exec/plan_stage.h
@@ -109,6 +109,17 @@ public:
PlanStage(const char* typeName, OperationContext* opCtx)
: _commonStats(typeName), _opCtx(opCtx) {}
+protected:
+ /**
+ * Obtain a PlanStage given a child stage. Called during the construction of derived
+ * PlanStage types with a single direct descendant.
+ */
+ PlanStage(OperationContext* opCtx, std::unique_ptr<PlanStage> child, const char* typeName)
+ : PlanStage(typeName, opCtx) {
+ _children.push_back(std::move(child));
+ }
+
+public:
virtual ~PlanStage() {}
using Children = std::vector<std::unique_ptr<PlanStage>>;
diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp
index ac6e3f660f2..18debacd536 100644
--- a/src/mongo/db/exec/projection.cpp
+++ b/src/mongo/db/exec/projection.cpp
@@ -32,9 +32,12 @@
#include "mongo/db/exec/projection.h"
+#include "boost/optional.hpp"
+
#include "mongo/db/exec/plan_stage.h"
#include "mongo/db/exec/scoped_timer.h"
#include "mongo/db/exec/working_set_common.h"
+#include "mongo/db/exec/working_set_computed_data.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/record_id.h"
@@ -44,65 +47,82 @@
namespace mongo {
-using std::endl;
-using std::unique_ptr;
-using std::vector;
-using stdx::make_unique;
-
static const char* kIdField = "_id";
-// static
-const char* ProjectionStage::kStageType = "PROJECTION";
+namespace {
+
+BSONObj indexKey(const WorkingSetMember& member) {
+ return static_cast<const IndexKeyComputedData*>(member.getComputed(WSM_INDEX_KEY))->getKey();
+}
+
+BSONObj sortKey(const WorkingSetMember& member) {
+ return static_cast<const SortKeyComputedData*>(member.getComputed(WSM_SORT_KEY))->getSortKey();
+}
+
+double geoDistance(const WorkingSetMember& member) {
+ return static_cast<const GeoDistanceComputedData*>(
+ member.getComputed(WSM_COMPUTED_GEO_DISTANCE))
+ ->getDist();
+}
+
+BSONObj geoPoint(const WorkingSetMember& member) {
+ return static_cast<const GeoNearPointComputedData*>(member.getComputed(WSM_GEO_NEAR_POINT))
+ ->getPoint();
+}
+
+double textScore(const WorkingSetMember& member) {
+ if (member.hasComputed(WSM_COMPUTED_TEXT_SCORE))
+ return static_cast<const TextScoreComputedData*>(
+ member.getComputed(WSM_COMPUTED_TEXT_SCORE))
+ ->getScore();
+ // It is permitted to request a text score when none has been computed. Zero is returned as an
+ // empty value in this case.
+ else
+ return 0.0;
+}
+
+void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) {
+ member->keyData.clear();
+ member->recordId = RecordId();
+ member->obj = Snapshotted<BSONObj>(SnapshotId(), bo);
+ member->transitionToOwnedObj();
+}
+
+StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec,
+ const WorkingSetMember& member) {
+ if (exec.needsGeoNearDistance() && !member.hasComputed(WSM_COMPUTED_GEO_DISTANCE))
+ return Status(ErrorCodes::InternalError, "near loc dist requested but no data available");
+
+ if (exec.needsGeoNearPoint() && !member.hasComputed(WSM_GEO_NEAR_POINT))
+ return Status(ErrorCodes::InternalError, "near loc proj requested but no data available");
+
+ return member.hasObj()
+ ? exec.project(member.obj.value(),
+ exec.needsGeoNearDistance()
+ ? boost::optional<const double>(geoDistance(member))
+ : boost::none,
+ exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(),
+ exec.needsSortKey() ? sortKey(member) : BSONObj(),
+ exec.needsTextScore() ? boost::optional<const double>(textScore(member))
+ : boost::none,
+ member.recordId.repr())
+ : exec.projectCovered(
+ member.keyData,
+ exec.needsGeoNearDistance() ? boost::optional<const double>(geoDistance(member))
+ : boost::none,
+ exec.needsGeoNearPoint() ? geoPoint(member) : BSONObj(),
+ exec.needsSortKey() ? sortKey(member) : BSONObj(),
+ exec.needsTextScore() ? boost::optional<const double>(textScore(member))
+ : boost::none,
+ member.recordId.repr());
+}
+} // namespace
ProjectionStage::ProjectionStage(OperationContext* opCtx,
- const ProjectionStageParams& params,
+ const BSONObj& projObj,
WorkingSet* ws,
- PlanStage* child)
- : PlanStage(kStageType, opCtx), _ws(ws), _projImpl(params.projImpl) {
- _children.emplace_back(child);
- _projObj = params.projObj;
-
- if (ProjectionStageParams::NO_FAST_PATH == _projImpl) {
- _exec.reset(
- new ProjectionExec(opCtx, params.projObj, params.fullExpression, params.collator));
- } else {
- // We shouldn't need the full expression if we're fast-pathing.
- invariant(NULL == params.fullExpression);
-
- // Sanity-check the input.
- invariant(_projObj.isOwned());
- invariant(!_projObj.isEmpty());
-
- // Figure out what fields are in the projection.
- getSimpleInclusionFields(_projObj, &_includedFields);
-
- // If we're pulling data out of one index we can pre-compute the indices of the fields
- // in the key that we pull data from and avoid looking up the field name each time.
- if (ProjectionStageParams::COVERED_ONE_INDEX == params.projImpl) {
- // Sanity-check.
- _coveredKeyObj = params.coveredKeyObj;
- invariant(_coveredKeyObj.isOwned());
-
- BSONObjIterator kpIt(_coveredKeyObj);
- while (kpIt.more()) {
- BSONElement elt = kpIt.next();
- auto fieldIt = _includedFields.find(elt.fieldNameStringData());
- if (_includedFields.end() == fieldIt) {
- // Push an unused value on the back to keep _includeKey and _keyFieldNames
- // in sync.
- _keyFieldNames.push_back(StringData());
- _includeKey.push_back(false);
- } else {
- // If we are including this key field store its field name.
- _keyFieldNames.push_back(fieldIt->first);
- _includeKey.push_back(true);
- }
- }
- } else {
- invariant(ProjectionStageParams::SIMPLE_DOC == params.projImpl);
- }
- }
-}
+ std::unique_ptr<PlanStage> child)
+ : PlanStage(opCtx, std::move(child), kStageType), _projObj(projObj), _ws(*ws) {}
// static
void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields) {
@@ -128,63 +148,6 @@ void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet*
}
}
-// static
-void ProjectionStage::transformSimpleInclusion(const BSONObj& in,
- const FieldSet& includedFields,
- BSONObjBuilder& bob) {
- // Look at every field in the source document and see if we're including it.
- BSONObjIterator inputIt(in);
- while (inputIt.more()) {
- BSONElement elt = inputIt.next();
- auto fieldIt = includedFields.find(elt.fieldNameStringData());
- if (includedFields.end() != fieldIt) {
- // If so, add it to the builder.
- bob.append(elt);
- }
- }
-}
-
-Status ProjectionStage::transform(WorkingSetMember* member) {
- // The default no-fast-path case.
- if (ProjectionStageParams::NO_FAST_PATH == _projImpl) {
- return _exec->transform(member);
- }
-
- BSONObjBuilder bob;
-
- // SIMPLE_DOC implies that we expect an object so it's kind of redundant.
- if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) {
- // If we got here because of SIMPLE_DOC the planner shouldn't have messed up.
- invariant(member->hasObj());
-
- // Apply the SIMPLE_DOC projection.
- transformSimpleInclusion(member->obj.value(), _includedFields, bob);
- } else {
- invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl);
- // We're pulling data out of the key.
- invariant(1 == member->keyData.size());
- size_t keyIndex = 0;
-
- // Look at every key element...
- BSONObjIterator keyIterator(member->keyData[0].keyData);
- while (keyIterator.more()) {
- BSONElement elt = keyIterator.next();
- // If we're supposed to include it...
- if (_includeKey[keyIndex]) {
- // Do so.
- bob.appendAs(elt, _keyFieldNames[keyIndex]);
- }
- ++keyIndex;
- }
- }
-
- member->keyData.clear();
- member->recordId = RecordId();
- member->obj = Snapshotted<BSONObj>(SnapshotId(), bob.obj());
- member->transitionToOwnedObj();
- return Status::OK();
-}
-
bool ProjectionStage::isEOF() {
return child()->isEOF();
}
@@ -196,12 +159,12 @@ PlanStage::StageState ProjectionStage::doWork(WorkingSetID* out) {
// Note that we don't do the normal if isEOF() return EOF thing here. Our child might be a
// tailable cursor and isEOF() would be true even if it had more data...
if (PlanStage::ADVANCED == status) {
- WorkingSetMember* member = _ws->get(id);
+ WorkingSetMember* member = _ws.get(id);
// Punt to our specific projection impl.
Status projStatus = transform(member);
if (!projStatus.isOK()) {
warning() << "Couldn't execute projection, status = " << redact(projStatus);
- *out = WorkingSetCommon::allocateStatusMember(_ws, projStatus);
+ *out = WorkingSetCommon::allocateStatusMember(&_ws, projStatus);
return PlanStage::FAILURE;
}
@@ -218,11 +181,11 @@ PlanStage::StageState ProjectionStage::doWork(WorkingSetID* out) {
return status;
}
-unique_ptr<PlanStageStats> ProjectionStage::getStats() {
+std::unique_ptr<PlanStageStats> ProjectionStage::getStats() {
_commonStats.isEOF = isEOF();
- unique_ptr<PlanStageStats> ret = make_unique<PlanStageStats>(_commonStats, STAGE_PROJECTION);
+ auto ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_PROJECTION);
- unique_ptr<ProjectionStats> projStats = make_unique<ProjectionStats>(_specificStats);
+ auto projStats = std::make_unique<ProjectionStats>(_specificStats);
projStats->projObj = _projObj;
ret->specific = std::move(projStats);
@@ -230,8 +193,128 @@ unique_ptr<PlanStageStats> ProjectionStage::getStats() {
return ret;
}
-const SpecificStats* ProjectionStage::getSpecificStats() const {
- return &_specificStats;
+ProjectionStageDefault::ProjectionStageDefault(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child,
+ const MatchExpression* fullExpression,
+ const CollatorInterface* collator)
+ : ProjectionStage(opCtx, projObj, ws, std::move(child)),
+ _exec(opCtx, projObj, fullExpression, collator) {}
+
+Status ProjectionStageDefault::transform(WorkingSetMember* member) const {
+ // The default no-fast-path case.
+ if (_exec.needsSortKey() && !member->hasComputed(WSM_SORT_KEY))
+ return Status(ErrorCodes::InternalError,
+ "sortKey meta-projection requested but no data available");
+
+ if (_exec.returnKey()) {
+ auto keys = _exec.computeReturnKeyProjection(
+ member->hasComputed(WSM_INDEX_KEY) ? indexKey(*member) : BSONObj(),
+ _exec.needsSortKey() ? sortKey(*member) : BSONObj());
+ if (!keys.isOK())
+ return keys.getStatus();
+
+ transitionMemberToOwnedObj(keys.getValue(), member);
+ return Status::OK();
+ }
+
+ auto projected = provideMetaFieldsAndPerformExec(_exec, *member);
+
+ if (!projected.isOK())
+ return projected.getStatus();
+
+ transitionMemberToOwnedObj(projected.getValue(), member);
+
+ return Status::OK();
+}
+
+ProjectionStageCovered::ProjectionStageCovered(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child,
+ const BSONObj& coveredKeyObj)
+ : ProjectionStage(opCtx, projObj, ws, std::move(child)), _coveredKeyObj(coveredKeyObj) {
+ invariant(projObjHasOwnedData());
+ // Figure out what fields are in the projection.
+ getSimpleInclusionFields(_projObj, &_includedFields);
+
+ // If we're pulling data out of one index we can pre-compute the indices of the fields
+ // in the key that we pull data from and avoid looking up the field name each time.
+
+ // Sanity-check.
+ invariant(_coveredKeyObj.isOwned());
+
+ BSONObjIterator kpIt(_coveredKeyObj);
+ while (kpIt.more()) {
+ BSONElement elt = kpIt.next();
+ auto fieldIt = _includedFields.find(elt.fieldNameStringData());
+ if (_includedFields.end() == fieldIt) {
+ // Push an unused value on the back to keep _includeKey and _keyFieldNames
+ // in sync.
+ _keyFieldNames.push_back(StringData());
+ _includeKey.push_back(false);
+ } else {
+ // If we are including this key field store its field name.
+ _keyFieldNames.push_back(fieldIt->first);
+ _includeKey.push_back(true);
+ }
+ }
+}
+
+Status ProjectionStageCovered::transform(WorkingSetMember* member) const {
+ BSONObjBuilder bob;
+
+ // We're pulling data out of the key.
+ invariant(1 == member->keyData.size());
+ size_t keyIndex = 0;
+
+ // Look at every key element...
+ BSONObjIterator keyIterator(member->keyData[0].keyData);
+ while (keyIterator.more()) {
+ BSONElement elt = keyIterator.next();
+ // If we're supposed to include it...
+ if (_includeKey[keyIndex]) {
+ // Do so.
+ bob.appendAs(elt, _keyFieldNames[keyIndex]);
+ }
+ ++keyIndex;
+ }
+
+ transitionMemberToOwnedObj(bob.obj(), member);
+ return Status::OK();
+}
+
+ProjectionStageSimple::ProjectionStageSimple(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child)
+ : ProjectionStage(opCtx, projObj, ws, std::move(child)) {
+ invariant(projObjHasOwnedData());
+ // Figure out what fields are in the projection.
+ getSimpleInclusionFields(_projObj, &_includedFields);
+}
+
+Status ProjectionStageSimple::transform(WorkingSetMember* member) const {
+ BSONObjBuilder bob;
+ // SIMPLE_DOC implies that we expect an object so it's kind of redundant.
+ // If we got here because of SIMPLE_DOC the planner shouldn't have messed up.
+ invariant(member->hasObj());
+
+ // Apply the SIMPLE_DOC projection.
+ // Look at every field in the source document and see if we're including it.
+ BSONObjIterator inputIt(member->obj.value());
+ while (inputIt.more()) {
+ BSONElement elt = inputIt.next();
+ auto fieldIt = _includedFields.find(elt.fieldNameStringData());
+ if (_includedFields.end() != fieldIt) {
+ // If so, add it to the builder.
+ bob.append(elt);
+ }
+ }
+
+ transitionMemberToOwnedObj(bob.obj(), member);
+ return Status::OK();
}
} // namespace mongo
diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h
index e8caf8b5db6..b28cdcd9302 100644
--- a/src/mongo/db/exec/projection.h
+++ b/src/mongo/db/exec/projection.h
@@ -41,46 +41,19 @@ namespace mongo {
class CollatorInterface;
-struct ProjectionStageParams {
- enum ProjectionImplementation {
- // The default case. Will handle every projection.
- NO_FAST_PATH,
-
- // The projection is simple inclusion and is totally covered by one index.
- COVERED_ONE_INDEX,
-
- // The projection is simple inclusion and we expect an object.
- SIMPLE_DOC
- };
-
- ProjectionImplementation projImpl = NO_FAST_PATH;
-
- // The projection object. We lack a ProjectionExpression or similar so we use a BSONObj.
- BSONObj projObj;
-
- // If we have a positional or elemMatch projection we need a MatchExpression to pull out the
- // right data.
- // Not owned here, we do not take ownership.
- const MatchExpression* fullExpression = nullptr;
-
- // If (COVERED_ONE_INDEX == projObj) this is the key pattern we're extracting covered data
- // from. Otherwise, this field is ignored.
- BSONObj coveredKeyObj;
-
- // The collator this operation should use to compare strings. If null, the collation is a simple
- // binary compare.
- const CollatorInterface* collator = nullptr;
-};
-
/**
- * This stage computes a projection.
+ * This stage computes a projection. This is an abstract base class for various projection
+ * implementations.
*/
-class ProjectionStage final : public PlanStage {
-public:
+class ProjectionStage : public PlanStage {
+protected:
ProjectionStage(OperationContext* opCtx,
- const ProjectionStageParams& params,
+ const BSONObj& projObj,
WorkingSet* ws,
- PlanStage* child);
+ std::unique_ptr<PlanStage> child);
+
+public:
+ static constexpr const char* kStageType = "PROJECTION";
bool isEOF() final;
StageState doWork(WorkingSetID* out) final;
@@ -89,10 +62,13 @@ public:
return STAGE_PROJECTION;
}
- std::unique_ptr<PlanStageStats> getStats();
+ std::unique_ptr<PlanStageStats> getStats() final;
- const SpecificStats* getSpecificStats() const final;
+ const SpecificStats* getSpecificStats() const final {
+ return &_specificStats;
+ }
+protected:
using FieldSet = StringMap<bool>; // Value is unused.
/**
@@ -102,42 +78,74 @@ public:
*/
static void getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields);
+ bool projObjHasOwnedData() {
+ return _projObj.isOwned() && !_projObj.isEmpty();
+ }
+
+ // The projection object used by all projection implementations. We lack a ProjectionExpression
+ // or similar so we use a BSONObj.
+ BSONObj _projObj;
+
+private:
/**
- * Applies a simple inclusion projection to 'in', including
- * only the fields specified by 'includedFields'.
- *
- * The resulting document is constructed using 'bob'.
+ * Runs either the default complete implementation or a fast path depending on how this was
+ * constructed.
*/
- static void transformSimpleInclusion(const BSONObj& in,
- const FieldSet& includedFields,
- BSONObjBuilder& bob);
+ virtual Status transform(WorkingSetMember* member) const = 0;
- static const char* kStageType;
+ // Used to retrieve a WorkingSetMember as part of 'doWork()'.
+ WorkingSet& _ws;
-private:
- Status transform(WorkingSetMember* member);
+ // Populated by 'getStats()'.
+ ProjectionStats _specificStats;
+};
- std::unique_ptr<ProjectionExec> _exec;
+/**
+ * The default case. Can handle every projection.
+ */
+class ProjectionStageDefault final : public ProjectionStage {
+public:
+ /**
+ * ProjectionNode::DEFAULT should use this for construction.
+ */
+ ProjectionStageDefault(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child,
+ const MatchExpression* fullExpression,
+ const CollatorInterface* collator);
- // _ws is not owned by us.
- WorkingSet* _ws;
+private:
+ Status transform(WorkingSetMember* member) const final;
- // Stats
- ProjectionStats _specificStats;
+ // Fully-general heavy execution object.
+ ProjectionExec _exec;
+};
- // Fast paths:
- ProjectionStageParams::ProjectionImplementation _projImpl;
+/**
+ * This class is used when the projection is totally covered by one index and the following rules
+ * are met: the projection consists only of inclusions e.g. '{field: 1}', it has no $meta
+ * projections, it is not a returnKey projection and it has no dotted fields.
+ */
+class ProjectionStageCovered final : public ProjectionStage {
+public:
+ /**
+ * ProjectionNode::COVERED_ONE_INDEX should obtain a fast-path object through this constructor.
+ */
+ ProjectionStageCovered(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child,
+ const BSONObj& coveredKeyObj);
- // Used by all projection implementations.
- BSONObj _projObj;
+private:
+ Status transform(WorkingSetMember* member) const final;
- // Data used for both SIMPLE_DOC and COVERED_ONE_INDEX paths.
// Has the field names present in the simple projection.
FieldSet _includedFields;
- //
- // Used for the COVERED_ONE_INDEX path.
- //
+ // This is the key pattern we're extracting covered data from. It is maintained here since
+ // strings derived from it depend on its lifetime.
BSONObj _coveredKeyObj;
// Field names can be empty in 2.4 and before so we can't use them as a sentinel value.
@@ -148,4 +156,26 @@ private:
std::vector<StringData> _keyFieldNames;
};
+/**
+ * This class is used when we expect an object and the following rules are met: the projection
+ * consists only of inclusions e.g. '{field: 1}', it has no $meta projections, it is not a returnKey
+ * projection and it has no dotted fields.
+ */
+class ProjectionStageSimple final : public ProjectionStage {
+public:
+ /**
+ * ProjectionNode::SIMPLE_DOC should obtain a fast-path object through this constructor.
+ */
+ ProjectionStageSimple(OperationContext* opCtx,
+ const BSONObj& projObj,
+ WorkingSet* ws,
+ std::unique_ptr<PlanStage> child);
+
+private:
+ Status transform(WorkingSetMember* member) const final;
+
+ // Has the field names present in the simple projection.
+ FieldSet _includedFields;
+};
+
} // namespace mongo
diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp
index 83e5ad721e9..e1240539f38 100644
--- a/src/mongo/db/exec/projection_exec.cpp
+++ b/src/mongo/db/exec/projection_exec.cpp
@@ -31,7 +31,6 @@
#include "mongo/db/exec/projection_exec.h"
#include "mongo/bson/mutable/document.h"
-#include "mongo/db/exec/working_set_computed_data.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/query/collation/collator_interface.h"
@@ -41,58 +40,15 @@
namespace mongo {
-using std::max;
using std::string;
namespace mmb = mongo::mutablebson;
-namespace {
-
-/**
- * Adds sort key metadata inside 'member' to 'builder' with field name 'fieldName'.
- *
- * Returns a non-OK status if sort key metadata is missing from 'member'.
- */
-Status addSortKeyMetaProj(StringData fieldName,
- const WorkingSetMember& member,
- BSONObjBuilder* builder) {
- if (!member.hasComputed(WSM_SORT_KEY)) {
- return Status(ErrorCodes::InternalError,
- "sortKey meta-projection requested but no data available");
- }
-
- const SortKeyComputedData* sortKeyData =
- static_cast<const SortKeyComputedData*>(member.getComputed(WSM_SORT_KEY));
- builder->append(fieldName, sortKeyData->getSortKey());
- return Status::OK();
-}
-
-} // namespace
-
-ProjectionExec::ProjectionExec()
- : _include(true),
- _special(false),
- _includeID(true),
- _skip(0),
- _limit(-1),
- _arrayOpType(ARRAY_OP_NORMAL),
- _queryExpression(NULL),
- _hasReturnKey(false) {}
-
ProjectionExec::ProjectionExec(OperationContext* opCtx,
const BSONObj& spec,
const MatchExpression* queryExpression,
const CollatorInterface* collator)
- : _include(true),
- _special(false),
- _source(spec),
- _includeID(true),
- _skip(0),
- _limit(-1),
- _arrayOpType(ARRAY_OP_NORMAL),
- _queryExpression(queryExpression),
- _hasReturnKey(false),
- _collator(collator) {
+ : _source(spec), _queryExpression(queryExpression), _collator(collator) {
// Whether we're including or excluding fields.
enum class IncludeExclude { kUninitialized, kInclude, kExclude };
IncludeExclude includeExclude = IncludeExclude::kUninitialized;
@@ -103,7 +59,7 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx,
if (Object == e.type()) {
BSONObj obj = e.embeddedObject();
- verify(1 == obj.nFields());
+ invariant(1 == obj.nFields());
BSONElement e2 = obj.firstElement();
if (mongoutils::str::equals(e2.fieldName(), "$slice")) {
@@ -115,15 +71,15 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx,
add(e.fieldName(), 0, i);
}
} else {
- verify(e2.type() == Array);
+ invariant(e2.type() == Array);
BSONObj arr = e2.embeddedObject();
- verify(2 == arr.nFields());
+ invariant(2 == arr.nFields());
BSONObjIterator it(arr);
int skip = it.next().numberInt();
int limit = it.next().numberInt();
- verify(limit > 0);
+ invariant(limit > 0);
add(e.fieldName(), skip, limit);
}
@@ -132,39 +88,43 @@ ProjectionExec::ProjectionExec(OperationContext* opCtx,
// Create a MatchExpression for the elemMatch.
BSONObj elemMatchObj = e.wrap();
- verify(elemMatchObj.isOwned());
+ invariant(elemMatchObj.isOwned());
_elemMatchObjs.push_back(elemMatchObj);
boost::intrusive_ptr<ExpressionContext> expCtx(
new ExpressionContext(opCtx, _collator));
StatusWithMatchExpression statusWithMatcher =
MatchExpressionParser::parse(elemMatchObj, std::move(expCtx));
- verify(statusWithMatcher.isOK());
+ invariant(statusWithMatcher.isOK());
// And store it in _matchers.
_matchers[mongoutils::str::before(e.fieldName(), '.').c_str()] =
statusWithMatcher.getValue().release();
add(e.fieldName(), true);
} else if (mongoutils::str::equals(e2.fieldName(), "$meta")) {
- verify(String == e2.type());
+ invariant(String == e2.type());
if (e2.valuestr() == QueryRequest::metaTextScore) {
_meta[e.fieldName()] = META_TEXT_SCORE;
+ _needsTextScore = true;
} else if (e2.valuestr() == QueryRequest::metaSortKey) {
_sortKeyMetaFields.push_back(e.fieldName());
_meta[_sortKeyMetaFields.back()] = META_SORT_KEY;
+ _needsSortKey = true;
} else if (e2.valuestr() == QueryRequest::metaRecordId) {
_meta[e.fieldName()] = META_RECORDID;
} else if (e2.valuestr() == QueryRequest::metaGeoNearPoint) {
_meta[e.fieldName()] = META_GEONEAR_POINT;
+ _needsGeoNearPoint = true;
} else if (e2.valuestr() == QueryRequest::metaGeoNearDistance) {
_meta[e.fieldName()] = META_GEONEAR_DIST;
+ _needsGeoNearDistance = true;
} else if (e2.valuestr() == QueryRequest::metaIndexKey) {
_hasReturnKey = true;
} else {
// This shouldn't happen, should be caught by parsing.
- verify(0);
+ MONGO_UNREACHABLE;
}
} else {
- verify(0);
+ MONGO_UNREACHABLE;
}
} else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) {
_includeID = false;
@@ -240,147 +200,140 @@ void ProjectionExec::add(const string& field, int skip, int limit) {
// Execution
//
-Status ProjectionExec::transform(WorkingSetMember* member) const {
- if (_hasReturnKey) {
- BSONObjBuilder builder;
+StatusWith<BSONObj> ProjectionExec::computeReturnKeyProjection(const BSONObj& indexKey,
+ const BSONObj& sortKey) const {
+ BSONObjBuilder bob;
- if (member->hasComputed(WSM_INDEX_KEY)) {
- const IndexKeyComputedData* key =
- static_cast<const IndexKeyComputedData*>(member->getComputed(WSM_INDEX_KEY));
- builder.appendElements(key->getKey());
- }
+ if (!indexKey.isEmpty()) {
+ bob.appendElements(indexKey);
+ }
- // Must be possible to do both returnKey meta-projection and sortKey meta-projection so that
- // mongos can support returnKey.
- for (auto fieldName : _sortKeyMetaFields) {
- auto sortKeyMetaStatus = addSortKeyMetaProj(fieldName, *member, &builder);
- if (!sortKeyMetaStatus.isOK()) {
- return sortKeyMetaStatus;
- }
- }
+ // Must be possible to do both returnKey meta-projection and sortKey meta-projection so that
+ // mongos can support returnKey.
+ for (auto fieldName : _sortKeyMetaFields)
+ bob.append(fieldName, sortKey);
- member->obj = Snapshotted<BSONObj>(SnapshotId(), builder.obj());
- member->keyData.clear();
- member->recordId = RecordId();
- member->transitionToOwnedObj();
- return Status::OK();
- }
+ return bob.obj();
+}
+StatusWith<BSONObj> ProjectionExec::project(const BSONObj& in,
+ const boost::optional<const double> geoDistance,
+ const BSONObj& geoNearPoint,
+ const BSONObj& sortKey,
+ const boost::optional<const double> textScore,
+ const int64_t recordId) const {
BSONObjBuilder bob;
- if (member->hasObj()) {
- MatchDetails matchDetails;
-
- // If it's a positional projection we need a MatchDetails.
- if (transformRequiresDetails()) {
- matchDetails.requestElemMatchKey();
- verify(NULL != _queryExpression);
- verify(_queryExpression->matchesBSON(member->obj.value(), &matchDetails));
- }
+ MatchDetails matchDetails;
- Status projStatus = transform(member->obj.value(), &bob, &matchDetails);
- if (!projStatus.isOK()) {
- return projStatus;
- }
- } else {
- invariant(!_include);
- // Go field by field.
- if (_includeID) {
- BSONElement elt;
- // Sometimes the _id field doesn't exist...
- if (member->getFieldDotted("_id", &elt) && !elt.eoo()) {
- bob.appendAs(elt, "_id");
- }
+ // If it's a positional projection we need a MatchDetails.
+ if (transformRequiresDetails()) {
+ matchDetails.requestElemMatchKey();
+ invariant(nullptr != _queryExpression);
+ invariant(_queryExpression->matchesBSON(in, &matchDetails));
+ }
+
+ Status projStatus = projectHelper(in, &bob, &matchDetails);
+ if (!projStatus.isOK())
+ return projStatus;
+ else
+ return {addMeta(std::move(bob), geoDistance, geoNearPoint, sortKey, textScore, recordId)};
+}
+
+StatusWith<BSONObj> ProjectionExec::projectCovered(const std::vector<IndexKeyDatum>& keyData,
+ const boost::optional<const double> geoDistance,
+ const BSONObj& geoNearPoint,
+ const BSONObj& sortKey,
+ const boost::optional<const double> textScore,
+ const int64_t recordId) const {
+ invariant(!_include);
+ BSONObjBuilder bob;
+ // Go field by field.
+ if (_includeID) {
+ boost::optional<BSONElement> elt;
+ // Sometimes the _id field doesn't exist...
+ if ((elt = IndexKeyDatum::getFieldDotted(keyData, "_id")) && !elt->eoo()) {
+ bob.appendAs(elt.get(), "_id");
}
+ }
- mmb::Document projectedDoc;
+ mmb::Document projectedDoc;
- for (auto&& specElt : _source) {
- if (mongoutils::str::equals("_id", specElt.fieldName())) {
- continue;
- }
+ for (auto&& specElt : _source) {
+ if (mongoutils::str::equals("_id", specElt.fieldName())) {
+ continue;
+ }
- // $meta sortKey is the only meta-projection 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);
- continue;
- }
+ // $meta sortKey is the only meta-projection 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);
+ continue;
+ }
- // $meta sortKey is also the only element with an Object value in the projection spec
- // that can operate on index keys rather than the full document.
- invariant(BSONType::Object != specElt.type());
-
- BSONElement keyElt;
- // We can project a field that doesn't exist. We just ignore it.
- if (member->getFieldDotted(specElt.fieldName(), &keyElt) && !keyElt.eoo()) {
- FieldRef projectedFieldPath{specElt.fieldNameStringData()};
- auto setElementStatus =
- pathsupport::setElementAtPath(projectedFieldPath, keyElt, &projectedDoc);
- if (!setElementStatus.isOK()) {
- return setElementStatus;
- }
+ // $meta sortKey is also the only element with an Object value in the projection spec
+ // that can operate on index keys rather than the full document.
+ invariant(BSONType::Object != specElt.type());
+
+ boost::optional<BSONElement> keyElt;
+ // We can project a field that doesn't exist. We just ignore it.
+ if ((keyElt = IndexKeyDatum::getFieldDotted(keyData, specElt.fieldName())) &&
+ !keyElt->eoo()) {
+ FieldRef projectedFieldPath{specElt.fieldNameStringData()};
+ auto setElementStatus =
+ pathsupport::setElementAtPath(projectedFieldPath, keyElt.get(), &projectedDoc);
+ if (!setElementStatus.isOK()) {
+ return setElementStatus;
}
}
-
- bob.appendElements(projectedDoc.getObject());
}
+ bob.appendElements(projectedDoc.getObject());
+ return {addMeta(std::move(bob), geoDistance, geoNearPoint, sortKey, textScore, recordId)};
+}
+
+BSONObj ProjectionExec::addMeta(BSONObjBuilder bob,
+ const boost::optional<const double> geoDistance,
+ const BSONObj& geoNearPoint,
+ const BSONObj& sortKey,
+ const boost::optional<const double> textScore,
+ const int64_t recordId) const {
for (MetaMap::const_iterator it = _meta.begin(); it != _meta.end(); ++it) {
- if (META_GEONEAR_DIST == it->second) {
- if (member->hasComputed(WSM_COMPUTED_GEO_DISTANCE)) {
- const GeoDistanceComputedData* dist = static_cast<const GeoDistanceComputedData*>(
- member->getComputed(WSM_COMPUTED_GEO_DISTANCE));
- bob.append(it->first, dist->getDist());
- } else {
- return Status(ErrorCodes::InternalError,
- "near loc dist requested but no data available");
- }
- } else if (META_GEONEAR_POINT == it->second) {
- if (member->hasComputed(WSM_GEO_NEAR_POINT)) {
- const GeoNearPointComputedData* point =
- static_cast<const GeoNearPointComputedData*>(
- member->getComputed(WSM_GEO_NEAR_POINT));
- BSONObj ptObj = point->getPoint();
+ switch (it->second) {
+ case META_GEONEAR_DIST:
+ invariant(geoDistance);
+ bob.append(it->first, geoDistance.get());
+ break;
+ case META_GEONEAR_POINT: {
+ invariant(!geoNearPoint.isEmpty());
+ auto& ptObj = geoNearPoint;
if (ptObj.couldBeArray()) {
bob.appendArray(it->first, ptObj);
} else {
bob.append(it->first, ptObj);
}
- } else {
- return Status(ErrorCodes::InternalError,
- "near loc proj requested but no data available");
- }
- } else if (META_TEXT_SCORE == it->second) {
- if (member->hasComputed(WSM_COMPUTED_TEXT_SCORE)) {
- const TextScoreComputedData* score = static_cast<const TextScoreComputedData*>(
- member->getComputed(WSM_COMPUTED_TEXT_SCORE));
- bob.append(it->first, score->getScore());
- } else {
- bob.append(it->first, 0.0);
+ break;
}
- } else if (META_SORT_KEY == it->second) {
- auto sortKeyMetaStatus = addSortKeyMetaProj(it->first, *member, &bob);
- if (!sortKeyMetaStatus.isOK()) {
- return sortKeyMetaStatus;
+ case META_TEXT_SCORE:
+ invariant(textScore);
+ bob.append(it->first, textScore.get());
+ break;
+ case META_SORT_KEY: {
+ invariant(!sortKey.isEmpty());
+ bob.append(it->first, sortKey);
+ break;
}
- } else if (META_RECORDID == it->second) {
- bob.append(it->first, static_cast<long long>(member->recordId.repr()));
+ case META_RECORDID:
+ invariant(recordId != 0);
+ bob.append(it->first, recordId);
}
}
-
- BSONObj newObj = bob.obj();
- member->obj = Snapshotted<BSONObj>(SnapshotId(), newObj);
- member->keyData.clear();
- member->recordId = RecordId();
- member->transitionToOwnedObj();
-
- return Status::OK();
+ return bob.obj();
}
-Status ProjectionExec::transform(const BSONObj& in,
- BSONObjBuilder* bob,
- const MatchDetails* details) const {
+Status ProjectionExec::projectHelper(const BSONObj& in,
+ BSONObjBuilder* bob,
+ const MatchDetails* details) const {
const ArrayOpType& arrayOpType = _arrayOpType;
BSONObjIterator it(in);
@@ -451,7 +404,7 @@ void ProjectionExec::appendArray(BSONObjBuilder* bob, const BSONObj& array, bool
int limit = nested ? -1 : _limit;
if (skip < 0) {
- skip = max(0, skip + array.nFields());
+ skip = std::max(0, skip + array.nFields());
}
int index = 0;
diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h
index 93af3c083ed..74676d6d1fe 100644
--- a/src/mongo/db/exec/projection_exec.h
+++ b/src/mongo/db/exec/projection_exec.h
@@ -30,6 +30,8 @@
#pragma once
+#include "boost/optional.hpp"
+
#include "mongo/db/exec/working_set.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
@@ -40,6 +42,9 @@ namespace mongo {
class CollatorInterface;
+/**
+ * A fully-featured executor for find projection.
+ */
class ProjectionExec {
public:
/**
@@ -55,7 +60,6 @@ public:
enum MetaProjection {
META_GEONEAR_DIST,
META_GEONEAR_POINT,
- META_IX_KEY,
META_RECORDID,
META_SORT_KEY,
META_TEXT_SCORE,
@@ -76,16 +80,95 @@ public:
~ProjectionExec();
/**
- * Apply this projection to the 'member'. Changes the type to OWNED_OBJ.
+ * Indicates whether this is a returnKey projection which should be performed via
+ * 'computeReturnKeyProjection()'.
+ */
+ bool returnKey() const {
+ return _hasReturnKey;
+ }
+
+ /**
+ * Indicates whether 'sortKey' must be provided for 'computeReturnKeyProjection()' or
+ * 'project()'.
+ */
+ bool needsSortKey() const {
+ return _needsSortKey;
+ }
+
+ /**
+ * Indicates whether 'geoDistance' must be provided for 'project()'.
+ */
+ bool needsGeoNearDistance() const {
+ return _needsGeoNearDistance;
+ }
+
+ /**
+ * Indicates whether 'geoNearPoint' must be provided for 'project()'.
+ */
+ bool needsGeoNearPoint() const {
+ return _needsGeoNearPoint;
+ }
+
+ /**
+ * Indicates whether 'textScore' is going to be used in 'project()'.
+ */
+ bool needsTextScore() const {
+ return _needsTextScore;
+ }
+
+ /**
+ * Returns false if there are no meta fields to project.
+ */
+ bool hasMetaFields() const {
+ return !_meta.empty();
+ }
+
+ /**
+ * Performs a returnKey projection and provides index keys rather than projection results.
*/
- Status transform(WorkingSetMember* member) const;
+ StatusWith<BSONObj> computeReturnKeyProjection(const BSONObj& indexKey,
+ const BSONObj& sortKey) const;
+
+ /**
+ * Performs a projection given a BSONObj source. Meta fields must be provided if necessary.
+ * Their necessity can be queried via the 'needs*' functions.
+ */
+ StatusWith<BSONObj> project(const BSONObj& in,
+ const boost::optional<const double> geoDistance = boost::none,
+ const BSONObj& geoNearPoint = BSONObj(),
+ const BSONObj& sortKey = BSONObj(),
+ const boost::optional<const double> textScore = boost::none,
+ const int64_t recordId = 0) const;
+
+ /**
+ * Performs a projection given a function to retrieve fields by name. This function handles
+ * projections which do not qualify for the COVERED_ONE_INDEX fast-path but are still coverd by
+ * indices.
+ */
+ StatusWith<BSONObj> projectCovered(
+ const std::vector<IndexKeyDatum>& keyData,
+ const boost::optional<const double> geoDistance = boost::none,
+ const BSONObj& geoNearPoint = BSONObj(),
+ const BSONObj& sortKey = BSONObj(),
+ const boost::optional<const double> textScore = boost::none,
+ const int64_t recordId = 0) const;
private:
+ /**
+ * Adds meta fields to the end of a projection.
+ */
+ BSONObj addMeta(BSONObjBuilder bob,
+ const boost::optional<const double> geoDistance,
+ const BSONObj& geoNearPoint,
+ const BSONObj& sortKey,
+ const boost::optional<const double> textScore,
+ const int64_t recordId) const;
+
//
// Initialization
//
- ProjectionExec();
+ ProjectionExec() = default;
/**
* Add 'field' as a field name that is included or excluded as part of the projection.
@@ -110,9 +193,9 @@ private:
* 'bob'.
* Otherwise, returns error.
*/
- Status transform(const BSONObj& in,
- BSONObjBuilder* bob,
- const MatchDetails* details = NULL) const;
+ Status projectHelper(const BSONObj& in,
+ BSONObjBuilder* bob,
+ const MatchDetails* details = nullptr) const;
/**
* See transform(...) above.
@@ -137,10 +220,10 @@ private:
void appendArray(BSONObjBuilder* bob, const BSONObj& array, bool nested = false) const;
// True if default at this level is to include.
- bool _include;
+ bool _include = true;
// True if this level can't be skipped or included without recursing.
- bool _special;
+ bool _special = false;
// We must group projections with common prefixes together.
// TODO: benchmark std::vector<pair> vs map
@@ -154,11 +237,11 @@ private:
BSONObj _source;
// Should we include the _id field?
- bool _includeID;
+ bool _includeID = true;
// Arguments from the $slice operator.
- int _skip;
- int _limit;
+ int _skip = 0;
+ int _limit = -1;
// Used for $elemMatch and positional operator ($)
Matchers _matchers;
@@ -166,17 +249,24 @@ private:
// The matchers above point into BSONObjs and this is where those objs live.
std::vector<BSONObj> _elemMatchObjs;
- ArrayOpType _arrayOpType;
+ ArrayOpType _arrayOpType = ARRAY_OP_NORMAL;
- // The full query expression. Used when we need MatchDetails.
- const MatchExpression* _queryExpression;
+ // The full query expression. Used when we need MatchDetails.
+ const MatchExpression* _queryExpression = nullptr;
// Projections that aren't sourced from the document or index keys.
MetaMap _meta;
// Do we have a returnKey projection? If so we *only* output the index key metadata, and
// possibly the sort key for mongos to use. If it's not found we output nothing.
- bool _hasReturnKey;
+ bool _hasReturnKey = false;
+
+ // After parsing in the constructor, these fields will indicate the neccesity of metadata
+ // for $meta projection.
+ bool _needsSortKey = false;
+ bool _needsGeoNearDistance = false;
+ bool _needsGeoNearPoint = false;
+ bool _needsTextScore = false;
// The field names associated with any sortKey meta-projection(s). Empty if there is no sortKey
// meta-projection.
diff --git a/src/mongo/db/exec/projection_exec_test.cpp b/src/mongo/db/exec/projection_exec_test.cpp
index 9c9e4f5f090..9a05cfabe05 100644
--- a/src/mongo/db/exec/projection_exec_test.cpp
+++ b/src/mongo/db/exec/projection_exec_test.cpp
@@ -32,6 +32,10 @@
* This file contains tests for mongo/db/exec/projection_exec.cpp
*/
+#include "boost/optional.hpp"
+#include "boost/optional/optional_io.hpp"
+#include <memory>
+
#include "mongo/db/exec/projection_exec.h"
#include "mongo/db/exec/working_set_computed_data.h"
@@ -39,159 +43,56 @@
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
+#include "mongo/stdx/variant.h"
#include "mongo/unittest/unittest.h"
-#include <memory>
using namespace mongo;
+using namespace std::string_literals;
namespace {
-using std::unique_ptr;
-
/**
- * Utility function to create MatchExpression
+ * Utility function to create a MatchExpression.
*/
-unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) {
+std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
StatusWithMatchExpression status = MatchExpressionParser::parse(obj, std::move(expCtx));
ASSERT_TRUE(status.isOK());
return std::move(status.getValue());
}
-//
-// transform tests
-//
-
/**
- * test function to verify results of transform()
- * on a working set member.
- *
- * specStr - projection specification
- * queryStr - query
- * objStr - object to run projection on
- * data - computed data. Owned by working set member created in this function if not null.
- * expectedStatusOK - expected status of transformation
- * expectedObjStr - expected object after successful projection.
- * Ignored if expectedStatusOK is false.
+ * Test encapsulation for single call to ProjectionExec::project() or
+ * ProjectionExec::projectCovered().
*/
-
-void testTransform(const char* specStr,
- const char* queryStr,
- const char* objStr,
- WorkingSetComputedData* data,
- const CollatorInterface* collator,
- bool expectedStatusOK,
- const char* expectedObjStr) {
+boost::optional<std::string> project(
+ const char* specStr,
+ const char* queryStr,
+ const stdx::variant<const char*, const IndexKeyDatum> objStrOrDatum,
+ const boost::optional<const CollatorInterface&> collator = boost::none,
+ const BSONObj& sortKey = BSONObj(),
+ const double textScore = 0.0) {
// Create projection exec object.
BSONObj spec = fromjson(specStr);
BSONObj query = fromjson(queryStr);
- unique_ptr<MatchExpression> queryExpression = parseMatchExpression(query);
- QueryTestServiceContext serviceCtx;
- auto opCtx = serviceCtx.makeOperationContext();
- ProjectionExec exec(opCtx.get(), spec, queryExpression.get(), collator);
-
- // Create working set member.
- WorkingSetMember wsm;
- wsm.obj = Snapshotted<BSONObj>(SnapshotId(), fromjson(objStr));
- if (data) {
- wsm.addComputed(data);
- }
- wsm.transitionToOwnedObj();
-
- // Transform object
- Status status = exec.transform(&wsm);
-
- // There are fewer checks to perform if we are expected a failed status.
- if (!expectedStatusOK) {
- if (status.isOK()) {
- mongoutils::str::stream ss;
- ss << "expected transform() to fail but got success instead."
- << "\nprojection spec: " << specStr << "\nquery: " << queryStr
- << "\nobject before projection: " << objStr;
- FAIL(ss);
- }
- return;
- }
-
- // If we are expecting a successful transformation but got a failed status instead,
- // print out status message in assertion message.
- if (!status.isOK()) {
- mongoutils::str::stream ss;
- ss << "transform() test failed: unexpected failed status: " << status.toString()
- << "\nprojection spec: " << specStr << "\nquery: " << queryStr
- << "\nobject before projection: " << objStr
- << "\nexpected object after projection: " << expectedObjStr;
- FAIL(ss);
- }
-
- // Finally, we compare the projected object.
- const BSONObj& obj = wsm.obj.value();
- BSONObj expectedObj = fromjson(expectedObjStr);
- if (SimpleBSONObjComparator::kInstance.evaluate(obj != expectedObj)) {
- mongoutils::str::stream ss;
- ss << "transform() test failed: unexpected projected object."
- << "\nprojection spec: " << specStr << "\nquery: " << queryStr
- << "\nobject before projection: " << objStr
- << "\nexpected object after projection: " << expectedObjStr
- << "\nactual object after projection: " << obj.toString();
- FAIL(ss);
- }
-}
-
-/**
- * testTransform without computed data or collator arguments.
- */
-void testTransform(const char* specStr,
- const char* queryStr,
- const char* objStr,
- bool expectedStatusOK,
- const char* expectedObjStr) {
- testTransform(specStr, queryStr, objStr, nullptr, nullptr, expectedStatusOK, expectedObjStr);
-}
-
-/**
- * Test function to verify the results of projecting the $meta sortKey while under a covered
- * projection. In particular, it tests that ProjectionExec can take a WorkingSetMember in
- * RID_AND_IDX state and use the sortKey along with the index data to generate the final output
- * document. For SERVER-20117.
- *
- * sortKey - The sort key in BSONObj form.
- * projSpec - The JSON representation of the proj spec BSONObj.
- * ikd - The data stored in the index.
- *
- * Returns the BSON representation of the actual output, to be checked against the expected output.
- */
-BSONObj transformMetaSortKeyCovered(const BSONObj& sortKey,
- const char* projSpec,
- const IndexKeyDatum& ikd) {
- WorkingSet ws;
- WorkingSetID wsid = ws.allocate();
- WorkingSetMember* wsm = ws.get(wsid);
- wsm->keyData.push_back(ikd);
- wsm->addComputed(new SortKeyComputedData(sortKey));
- ws.transitionToRecordIdAndIdx(wsid);
-
+ std::unique_ptr<MatchExpression> queryExpression = parseMatchExpression(query);
QueryTestServiceContext serviceCtx;
auto opCtx = serviceCtx.makeOperationContext();
- ProjectionExec projExec(opCtx.get(), fromjson(projSpec), nullptr, nullptr);
- ASSERT_OK(projExec.transform(wsm));
-
- return wsm->obj.value();
-}
-
-BSONObj transformCovered(BSONObj projSpec, const IndexKeyDatum& ikd) {
- WorkingSet ws;
- WorkingSetID wsid = ws.allocate();
- WorkingSetMember* wsm = ws.get(wsid);
- wsm->keyData.push_back(ikd);
- ws.transitionToRecordIdAndIdx(wsid);
-
- QueryTestServiceContext serviceCtx;
- auto opCtx = serviceCtx.makeOperationContext();
- ProjectionExec projExec(opCtx.get(), projSpec, nullptr, nullptr);
- ASSERT_OK(projExec.transform(wsm));
-
- return wsm->obj.value();
+ ProjectionExec exec(opCtx.get(), spec, queryExpression.get(), collator.get_ptr());
+
+ auto objStr = stdx::get_if<const char*>(&objStrOrDatum);
+ auto projected = objStr
+ ? exec.project(fromjson(*objStr), boost::none, BSONObj(), sortKey, textScore)
+ : exec.projectCovered({stdx::get<const IndexKeyDatum>(objStrOrDatum)},
+ boost::none,
+ BSONObj(),
+ sortKey,
+ textScore);
+
+ if (!projected.isOK())
+ return boost::none;
+ else
+ return boost::make_optional(projected.getValue().toString());
}
//
@@ -200,13 +101,15 @@ BSONObj transformCovered(BSONObj projSpec, const IndexKeyDatum& ikd) {
TEST(ProjectionExecTest, TransformPositionalDollar) {
// Valid position $ projections.
- testTransform("{'a.$': 1}", "{a: 10}", "{a: [10, 20, 30]}", true, "{a: [10]}");
- testTransform("{'a.$': 1}", "{a: 20}", "{a: [10, 20, 30]}", true, "{a: [20]}");
- testTransform("{'a.$': 1}", "{a: 30}", "{a: [10, 20, 30]}", true, "{a: [30]}");
- testTransform("{'a.$': 1}", "{a: {$gt: 4}}", "{a: [5]}", true, "{a: [5]}");
+ ASSERT_EQ(boost::make_optional("{ a: [ 10 ] }"s),
+ project("{'a.$': 1}", "{a: 10}", "{a: [10, 20, 30]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 20 ] }"s),
+ project("{'a.$': 1}", "{a: 20}", "{a: [10, 20, 30]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 5 ] }"s),
+ project("{'a.$': 1}", "{a: {$gt: 4}}", "{a: [5]}"));
// Invalid position $ projections.
- testTransform("{'a.$': 1}", "{a: {$size: 1}}", "{a: [5]}", false, "");
+ ASSERT_EQ(boost::none, project("{'a.$': 1}", "{a: {$size: 1}}", "{a: [5]}"));
}
//
@@ -217,24 +120,25 @@ TEST(ProjectionExecTest, TransformElemMatch) {
const char* s = "{a: [{x: 1, y: 10}, {x: 1, y: 20}, {x: 2, y: 10}]}";
// Valid $elemMatch projections.
- testTransform("{a: {$elemMatch: {x: 1}}}", "{}", s, true, "{a: [{x: 1, y: 10}]}");
- testTransform("{a: {$elemMatch: {x: 1, y: 20}}}", "{}", s, true, "{a: [{x: 1, y: 20}]}");
- testTransform("{a: {$elemMatch: {x: 2}}}", "{}", s, true, "{a: [{x: 2, y: 10}]}");
- testTransform("{a: {$elemMatch: {x: 3}}}", "{}", s, true, "{}");
+ ASSERT_EQ(boost::make_optional("{ a: [ { x: 1, y: 10 } ] }"s),
+ project("{a: {$elemMatch: {x: 1}}}", "{}", s));
+ ASSERT_EQ(boost::make_optional("{ a: [ { x: 1, y: 20 } ] }"s),
+ project("{a: {$elemMatch: {x: 1, y: 20}}}", "{}", s));
+ ASSERT_EQ(boost::make_optional("{ a: [ { x: 2, y: 10 } ] }"s),
+ project("{a: {$elemMatch: {x: 2}}}", "{}", s));
+ ASSERT_EQ(boost::make_optional("{}"s), project("{a: {$elemMatch: {x: 3}}}", "{}", s));
// $elemMatch on unknown field z
- testTransform("{a: {$elemMatch: {z: 1}}}", "{}", s, true, "{}");
+ ASSERT_EQ(boost::make_optional("{}"s), project("{a: {$elemMatch: {z: 1}}}", "{}", s));
}
TEST(ProjectionExecTest, ElemMatchProjectionRespectsCollator) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- testTransform("{a: {$elemMatch: {$gte: 'abc'}}}",
- "{}",
- "{a: ['zaa', 'zbb', 'zdd', 'zee']}",
- nullptr, // WSM computed data
- &collator,
- true,
- "{a: ['zdd']}");
+ ASSERT_EQ(boost::make_optional("{ a: [ \"zdd\" ] }"s),
+ project("{a: {$elemMatch: {$gte: 'abc'}}}",
+ "{}",
+ "{a: ['zaa', 'zbb', 'zdd', 'zee']}",
+ collator));
}
//
@@ -243,27 +147,42 @@ TEST(ProjectionExecTest, ElemMatchProjectionRespectsCollator) {
TEST(ProjectionExecTest, TransformSliceCount) {
// Valid $slice projections using format {$slice: count}.
- testTransform("{a: {$slice: -10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
- testTransform("{a: {$slice: -3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
- testTransform("{a: {$slice: -1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}");
- testTransform("{a: {$slice: 0}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
- testTransform("{a: {$slice: 1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}");
- testTransform("{a: {$slice: 3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
- testTransform("{a: {$slice: 10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: -10}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: -3}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 8 ] }"s),
+ project("{a: {$slice: -1}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [] }"s),
+ project("{a: {$slice: 0}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4 ] }"s),
+ project("{a: {$slice: 1}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: 3}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: 10}}", "{}", "{a: [4, 6, 8]}"));
}
TEST(ProjectionExecTest, TransformSliceSkipLimit) {
// Valid $slice projections using format {$slice: [skip, limit]}.
// Non-positive limits are rejected at the query parser and therefore not handled by
// the projection execution stage. In fact, it will abort on an invalid limit.
- testTransform("{a: {$slice: [-10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
- testTransform("{a: {$slice: [-3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
- testTransform("{a: {$slice: [-1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}");
- testTransform("{a: {$slice: [0, 2]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6]}");
- testTransform("{a: {$slice: [0, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}");
- testTransform("{a: {$slice: [1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [6]}");
- testTransform("{a: {$slice: [3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
- testTransform("{a: {$slice: [10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: [-10, 10]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6, 8 ] }"s),
+ project("{a: {$slice: [-3, 5]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 8 ] }"s),
+ project("{a: {$slice: [-1, 1]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4, 6 ] }"s),
+ project("{a: {$slice: [0, 2]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 4 ] }"s),
+ project("{a: {$slice: [0, 1]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [ 6 ] }"s),
+ project("{a: {$slice: [1, 1]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [] }"s),
+ project("{a: {$slice: [3, 5]}}", "{}", "{a: [4, 6, 8]}"));
+ ASSERT_EQ(boost::make_optional("{ a: [] }"s),
+ project("{a: {$slice: [10, 10]}}", "{}", "{a: [4, 6, 8]}"));
}
//
@@ -271,19 +190,19 @@ TEST(ProjectionExecTest, TransformSliceSkipLimit) {
//
TEST(ProjectionExecTest, TransformCoveredDottedProjection) {
- BSONObj projection = fromjson("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}");
BSONObj keyPattern = fromjson("{a: 1, 'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}");
BSONObj keyData = fromjson("{'': 1, '': 2, '': 3, '': 4, '': 5}");
- BSONObj result = transformCovered(projection, IndexKeyDatum(keyPattern, keyData, nullptr));
- ASSERT_BSONOBJ_EQ(result, fromjson("{b: {c: 2, d: 3, f: {g: 4, h: 5}}}"));
+ ASSERT_EQ(boost::make_optional("{ b: { c: 2, d: 3, f: { g: 4, h: 5 } } }"s),
+ project("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}",
+ "{}",
+ IndexKeyDatum(keyPattern, keyData, nullptr)));
}
TEST(ProjectionExecTest, TransformNonCoveredDottedProjection) {
- testTransform("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}",
- "{}",
- "{a: 1, b: {c: 2, d: 3, f: {g: 4, h: 5}}}",
- true,
- "{b: {c: 2, d: 3, f: {g: 4, h: 5}}}");
+ ASSERT_EQ(boost::make_optional("{ b: { c: 2, d: 3, f: { g: 4, h: 5 } } }"s),
+ project("{'b.c': 1, 'b.d': 1, 'b.f.g': 1, 'b.f.h': 1}",
+ "{}",
+ "{a: 1, b: {c: 2, d: 3, f: {g: 4, h: 5}}}"));
}
//
@@ -293,97 +212,97 @@ TEST(ProjectionExecTest, TransformNonCoveredDottedProjection) {
TEST(ProjectionExecTest, TransformMetaTextScore) {
// Query {} is ignored.
- testTransform("{b: {$meta: 'textScore'}}",
- "{}",
- "{a: 'hello'}",
- new mongo::TextScoreComputedData(100),
- nullptr, // collator
- true,
- "{a: 'hello', b: 100}");
+ ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: 100.0 }"s),
+ project("{b: {$meta: 'textScore'}}",
+ "{}",
+ "{a: 'hello'}",
+ boost::none, // collator
+ BSONObj(), // sortKey
+ 100.0)); // textScore
// Projected meta field should overwrite existing field.
- testTransform("{b: {$meta: 'textScore'}}",
- "{}",
- "{a: 'hello', b: -1}",
- new mongo::TextScoreComputedData(100),
- nullptr, // collator
- true,
- "{a: 'hello', b: 100}");
+ ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: 100.0 }"s),
+ project("{b: {$meta: 'textScore'}}",
+ "{}",
+ "{a: 'hello', b: -1}",
+ boost::none, // collator
+ BSONObj(), // sortKey
+ 100.0)); // textScore
}
TEST(ProjectionExecTest, TransformMetaSortKey) {
- testTransform("{b: {$meta: 'sortKey'}}",
- "{}",
- "{a: 'hello'}",
- new mongo::SortKeyComputedData(BSON("" << 99)),
- nullptr, // collator
- true,
- "{a: 'hello', b: {'': 99}}");
+ // Query {} is ignored.
+ ASSERT_EQ(boost::make_optional("{ a: \"hello\", b: { : 99 } }"s),
+ project("{b: {$meta: 'sortKey'}}",
+ "{}",
+ "{a: 'hello'}",
+ boost::none, // collator
+ BSON("" << 99))); // sortKey
// Projected meta field should overwrite existing field.
- testTransform("{a: {$meta: 'sortKey'}}",
- "{}",
- "{a: 'hello'}",
- new mongo::SortKeyComputedData(BSON("" << 99)),
- nullptr, // collator
- true,
- "{a: {'': 99}}");
+ ASSERT_EQ(boost::make_optional("{ a: { : 99 } }"s),
+ project("{a: {$meta: 'sortKey'}}",
+ "{}",
+ "{a: 'hello'}",
+ boost::none, // collator
+ BSON("" << 99))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredNormal) {
- BSONObj actualOut =
- transformMetaSortKeyCovered(BSON("" << 5),
- "{_id: 0, a: 1, b: {$meta: 'sortKey'}}",
- IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr));
- BSONObj expectedOut = BSON("a" << 5 << "b" << BSON("" << 5));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ a: 5, b: { : 5 } }"s),
+ project("{_id: 0, a: 1, b: {$meta: 'sortKey'}}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr),
+ boost::none, // collator
+ BSON("" << 5))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredOverwrite) {
- BSONObj actualOut =
- transformMetaSortKeyCovered(BSON("" << 5),
- "{_id: 0, a: 1, a: {$meta: 'sortKey'}}",
- IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr));
- BSONObj expectedOut = BSON("a" << BSON("" << 5));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ a: { : 5 } }"s),
+ project("{_id: 0, a: 1, a: {$meta: 'sortKey'}}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1), BSON("" << 5), nullptr),
+ boost::none, // collator
+ BSON("" << 5))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredAdditionalData) {
- BSONObj actualOut = transformMetaSortKeyCovered(
- BSON("" << 5),
- "{_id: 0, a: 1, b: {$meta: 'sortKey'}, c: 1}",
- IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr));
- BSONObj expectedOut = BSON("a" << 5 << "c" << 6 << "b" << BSON("" << 5));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ a: 5, c: 6, b: { : 5 } }"s),
+ project("{_id: 0, a: 1, b: {$meta: 'sortKey'}, c: 1}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr),
+ boost::none, // collator
+ BSON("" << 5))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound) {
- BSONObj actualOut = transformMetaSortKeyCovered(
- BSON("" << 5 << "" << 6),
- "{_id: 0, a: 1, b: {$meta: 'sortKey'}}",
- IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr));
- BSONObj expectedOut = BSON("a" << 5 << "b" << BSON("" << 5 << "" << 6));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ a: 5, b: { : 5, : 6 } }"s),
+ project("{_id: 0, a: 1, b: {$meta: 'sortKey'}}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1 << "c" << 1), BSON("" << 5 << "" << 6), nullptr),
+ boost::none, // collator
+ BSON("" << 5 << "" << 6))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound2) {
- BSONObj actualOut = transformMetaSortKeyCovered(
- BSON("" << 5 << "" << 6),
- "{_id: 0, a: 1, c: 1, b: {$meta: 'sortKey'}}",
- IndexKeyDatum(
- BSON("a" << 1 << "b" << 1 << "c" << 1), BSON("" << 5 << "" << 6 << "" << 4), nullptr));
- BSONObj expectedOut = BSON("a" << 5 << "c" << 4 << "b" << BSON("" << 5 << "" << 6));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ a: 5, c: 4, b: { : 5, : 6 } }"s),
+ project("{_id: 0, a: 1, c: 1, b: {$meta: 'sortKey'}}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1),
+ BSON("" << 5 << "" << 6 << "" << 4),
+ nullptr),
+ boost::none, // collator
+ BSON("" << 5 << "" << 6))); // sortKey
}
TEST(ProjectionExecTest, TransformMetaSortKeyCoveredCompound3) {
- BSONObj actualOut = transformMetaSortKeyCovered(
- BSON("" << 6 << "" << 4),
- "{_id: 0, c: 1, d: 1, b: {$meta: 'sortKey'}}",
- IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1),
- BSON("" << 5 << "" << 6 << "" << 4 << "" << 9000),
- nullptr));
- BSONObj expectedOut = BSON("c" << 4 << "d" << 9000 << "b" << BSON("" << 6 << "" << 4));
- ASSERT_BSONOBJ_EQ(actualOut, expectedOut);
+ ASSERT_EQ(boost::make_optional("{ c: 4, d: 9000, b: { : 6, : 4 } }"s),
+ project("{_id: 0, c: 1, d: 1, b: {$meta: 'sortKey'}}",
+ "{}",
+ IndexKeyDatum(BSON("a" << 1 << "b" << 1 << "c" << 1 << "d" << 1),
+ BSON("" << 5 << "" << 6 << "" << 4 << "" << 9000),
+ nullptr),
+ boost::none, // collator
+ BSON("" << 6 << "" << 4))); // sortKey
}
} // namespace
diff --git a/src/mongo/db/exec/working_set.cpp b/src/mongo/db/exec/working_set.cpp
index 893f39cb23b..bbf7012f6ce 100644
--- a/src/mongo/db/exec/working_set.cpp
+++ b/src/mongo/db/exec/working_set.cpp
@@ -186,23 +186,12 @@ bool WorkingSetMember::getFieldDotted(const string& field, BSONElement* out) con
}
// Our state should be such that we have index data/are covered.
- for (size_t i = 0; i < keyData.size(); ++i) {
- BSONObjIterator keyPatternIt(keyData[i].indexKeyPattern);
- BSONObjIterator keyDataIt(keyData[i].keyData);
-
- while (keyPatternIt.more()) {
- BSONElement keyPatternElt = keyPatternIt.next();
- verify(keyDataIt.more());
- BSONElement keyDataElt = keyDataIt.next();
-
- if (field == keyPatternElt.fieldName()) {
- *out = keyDataElt;
- return true;
- }
- }
+ if (auto outOpt = IndexKeyDatum::getFieldDotted(keyData, field)) {
+ *out = outOpt.get();
+ return true;
+ } else {
+ return false;
}
-
- return false;
}
size_t WorkingSetMember::getMemUsage() const {
diff --git a/src/mongo/db/exec/working_set.h b/src/mongo/db/exec/working_set.h
index 00c7bfe565e..a081f8dbed4 100644
--- a/src/mongo/db/exec/working_set.h
+++ b/src/mongo/db/exec/working_set.h
@@ -30,6 +30,7 @@
#pragma once
+#include "boost/optional.hpp"
#include <vector>
#include "mongo/base/disallow_copying.h"
@@ -148,6 +149,29 @@ struct IndexKeyDatum {
IndexKeyDatum(const BSONObj& keyPattern, const BSONObj& key, const IndexAccessMethod* index)
: indexKeyPattern(keyPattern), keyData(key), index(index) {}
+ /**
+ * getFieldDotted produces the field with the provided name based on index keyData. The return
+ * object is populated if the element is in a provided index key. Returns none otherwise.
+ * Returning none indicates a query planning error.
+ */
+ static boost::optional<BSONElement> getFieldDotted(const std::vector<IndexKeyDatum>& keyData,
+ const std::string& field) {
+ for (size_t i = 0; i < keyData.size(); ++i) {
+ BSONObjIterator keyPatternIt(keyData[i].indexKeyPattern);
+ BSONObjIterator keyDataIt(keyData[i].keyData);
+
+ while (keyPatternIt.more()) {
+ BSONElement keyPatternElt = keyPatternIt.next();
+ verify(keyDataIt.more());
+ BSONElement keyDataElt = keyDataIt.next();
+
+ if (field == keyPatternElt.fieldName())
+ return boost::make_optional(keyDataElt);
+ }
+ }
+ return boost::none;
+ }
+
// This is not owned and points into the IndexDescriptor's data.
BSONObj indexKeyPattern;
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index b9b1776f426..2bb817d1699 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -377,10 +377,7 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
// 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 (NULL != canonicalQuery->getProj()) {
- ProjectionStageParams params;
- params.projObj = canonicalQuery->getProj()->getProjObj();
- params.collator = canonicalQuery->getCollator();
+ if (nullptr != canonicalQuery->getProj()) {
// Add a SortKeyGeneratorStage if there is a $meta sortKey projection.
if (canonicalQuery->getProj()->wantSortKey()) {
@@ -397,13 +394,16 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx,
canonicalQuery->getProj()->wantIndexKey() ||
canonicalQuery->getProj()->wantSortKey() ||
canonicalQuery->getProj()->hasDottedFieldPath()) {
- params.fullExpression = canonicalQuery->root();
- params.projImpl = ProjectionStageParams::NO_FAST_PATH;
+ root = make_unique<ProjectionStageDefault>(opCtx,
+ canonicalQuery->getProj()->getProjObj(),
+ ws,
+ std::move(root),
+ canonicalQuery->root(),
+ canonicalQuery->getCollator());
} else {
- params.projImpl = ProjectionStageParams::SIMPLE_DOC;
+ root = make_unique<ProjectionStageSimple>(
+ opCtx, canonicalQuery->getProj()->getProjObj(), ws, std::move(root));
}
-
- root = make_unique<ProjectionStage>(opCtx, params, ws, root.release());
}
return PrepareExecutionResult(std::move(canonicalQuery), nullptr, std::move(root));
@@ -788,11 +788,12 @@ StatusWith<unique_ptr<PlanStage>> applyProjection(OperationContext* opCtx,
"Cannot use a $meta sortKey projection in findAndModify commands."};
}
- ProjectionStageParams params;
- params.projObj = proj;
- params.collator = cq->getCollator();
- params.fullExpression = cq->root();
- return {make_unique<ProjectionStage>(opCtx, params, ws, root.release())};
+ return {make_unique<ProjectionStageDefault>(opCtx,
+ proj,
+ ws,
+ std::unique_ptr<PlanStage>(root.release()),
+ cq->root(),
+ cq->getCollator())};
}
} // namespace
diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp
index 31a7ff7a8ab..eb3a5354fcb 100644
--- a/src/mongo/db/query/stage_builder.cpp
+++ b/src/mongo/db/query/stage_builder.cpp
@@ -140,29 +140,28 @@ PlanStage* buildStages(OperationContext* opCtx,
}
case STAGE_PROJECTION: {
const ProjectionNode* pn = static_cast<const ProjectionNode*>(root);
- PlanStage* childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws);
+ unique_ptr<PlanStage> childStage{
+ buildStages(opCtx, collection, cq, qsol, pn->children[0], ws)};
if (nullptr == childStage) {
return nullptr;
}
- ProjectionStageParams params;
- params.projObj = pn->projection;
- params.collator = cq.getCollator();
-
- // Stuff the right data into the params depending on what proj impl we use.
- if (ProjectionNode::DEFAULT == pn->projType) {
- params.fullExpression = pn->fullExpression;
- params.projImpl = ProjectionStageParams::NO_FAST_PATH;
- } else if (ProjectionNode::COVERED_ONE_INDEX == pn->projType) {
- params.projImpl = ProjectionStageParams::COVERED_ONE_INDEX;
- params.coveredKeyObj = pn->coveredKeyObj;
- invariant(!pn->coveredKeyObj.isEmpty());
- } else {
- invariant(ProjectionNode::SIMPLE_DOC == pn->projType);
- params.projImpl = ProjectionStageParams::SIMPLE_DOC;
+ switch (pn->projType) {
+ case ProjectionNode::DEFAULT:
+ return new ProjectionStageDefault(opCtx,
+ pn->projection,
+ ws,
+ std::move(childStage),
+ pn->fullExpression,
+ cq.getCollator());
+ case ProjectionNode::COVERED_ONE_INDEX:
+ invariant(!pn->coveredKeyObj.isEmpty());
+ return new ProjectionStageCovered(
+ opCtx, pn->projection, ws, std::move(childStage), pn->coveredKeyObj);
+ case ProjectionNode::SIMPLE_DOC:
+ return new ProjectionStageSimple(
+ opCtx, pn->projection, ws, std::move(childStage));
}
-
- return new ProjectionStage(opCtx, params, ws, childStage);
}
case STAGE_LIMIT: {
const LimitNode* ln = static_cast<const LimitNode*>(root);