diff options
author | Anton Korshunov <anton.korshunov@mongodb.com> | 2019-10-11 20:13:48 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-10-11 20:13:48 +0000 |
commit | 70383ed065e8798aafc4394af7c6d2ac92927f41 (patch) | |
tree | 6f7f64aea51387ed436efd36fdc24e6932489be1 | |
parent | 34719559a9ec4bd494a59d269d227d06b086affb (diff) | |
download | mongo-70383ed065e8798aafc4394af7c6d2ac92927f41.tar.gz |
SERVER-42423 Use ProjectionExecutor in ProjectionStageDefault
23 files changed, 308 insertions, 234 deletions
diff --git a/jstests/core/find_and_modify_server6865.js b/jstests/core/find_and_modify_server6865.js index 1c5d9363a6f..79d55c5b3a7 100644 --- a/jstests/core/find_and_modify_server6865.js +++ b/jstests/core/find_and_modify_server6865.js @@ -258,7 +258,7 @@ testFAMWorked({ update: {$set: {c: 'xyz'}}, new: true }, - {_id: 42, b: [{name: 'second', value: 2}], c: 'xyz'}); + {_id: 42, c: 'xyz', b: [{name: 'second', value: 2}]}); // Query on an array of objects while using $elemMatch in the projection, // where the matched array element is modified. diff --git a/src/mongo/base/exact_cast.h b/src/mongo/base/exact_cast.h index 99622670024..ff96eebe22e 100644 --- a/src/mongo/base/exact_cast.h +++ b/src/mongo/base/exact_cast.h @@ -41,7 +41,7 @@ namespace mongo { template <class DerivedPtr, class Base> auto exact_pointer_cast(Base* b) -> DerivedPtr { static_assert(std::is_pointer<DerivedPtr>::value); - using Derived = typename std::remove_pointer<DerivedPtr>::type; + using Derived = typename std::remove_cv<typename std::remove_pointer<DerivedPtr>::type>::type; static_assert(std::is_final<Derived>::value); if (b == nullptr) { diff --git a/src/mongo/db/exec/find_projection_executor.cpp b/src/mongo/db/exec/find_projection_executor.cpp index 098cdb93d71..9ff1bcd4602 100644 --- a/src/mongo/db/exec/find_projection_executor.cpp +++ b/src/mongo/db/exec/find_projection_executor.cpp @@ -128,17 +128,19 @@ Value applySliceProjectionHelper(const Document& input, size_t fieldPathIndex) { invariant(fieldPathIndex < params.path.getPathLength()); - auto fieldName = params.path.getFieldName(fieldPathIndex); + auto fieldName = params.path.getFieldName(fieldPathIndex++); Value val{input[fieldName]}; switch (val.getType()) { case BSONType::Array: - val = (fieldPathIndex + 1 == params.path.getPathLength()) + val = (fieldPathIndex == params.path.getPathLength()) ? sliceArray(val.getArray(), params.skip, params.limit) - : applySliceProjectionToArray(val.getArray(), params, fieldPathIndex + 1); + : applySliceProjectionToArray(val.getArray(), params, fieldPathIndex); break; case BSONType::Object: - val = applySliceProjectionHelper(val.getDocument(), params, fieldPathIndex + 1); + if (fieldPathIndex < params.path.getPathLength()) { + val = applySliceProjectionHelper(val.getDocument(), params, fieldPathIndex); + } break; default: break; diff --git a/src/mongo/db/exec/find_projection_executor_test.cpp b/src/mongo/db/exec/find_projection_executor_test.cpp index 2bff975ed8c..39194fbdd93 100644 --- a/src/mongo/db/exec/find_projection_executor_test.cpp +++ b/src/mongo/db/exec/find_projection_executor_test.cpp @@ -270,6 +270,10 @@ TEST(SliceProjection, CorrectlyProjectsSimplePath) { doc = Document{fromjson("{a: 1}")}; ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1}")}, applySliceProjection(doc, "a", boost::none, 2)); + + doc = Document{fromjson("{a: {b: 1}}")}; + ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: 1}}")}, + applySliceProjection(doc, "a", boost::none, 2)); } TEST(SliceProjection, CorrectlyProjectsDottedPath) { @@ -293,6 +297,10 @@ TEST(SliceProjection, CorrectlyProjectsDottedPath) { ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: 1}}")}, applySliceProjection(doc, "a.b", boost::none, 2)); + doc = Document{fromjson("{a: {b: {c: 1}}}")}; + ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: {c: 1}}}")}, + applySliceProjection(doc, "a.b", boost::none, 2)); + doc = Document{fromjson("{a: [{b: [1,2,3], c: 1}]}")}; ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3], c: 1}]}")}, applySliceProjection(doc, "a.b", boost::none, -1)); diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp index f334655562e..5d50aee547c 100644 --- a/src/mongo/db/exec/projection.cpp +++ b/src/mongo/db/exec/projection.cpp @@ -50,68 +50,68 @@ static const char* kIdField = "_id"; namespace { -BSONObj indexKey(const WorkingSetMember& member) { - return member.metadata().getIndexKey(); +void transitionMemberToOwnedObj(Document&& doc, WorkingSetMember* member) { + member->keyData.clear(); + member->recordId = {}; + member->doc = {{}, std::move(doc)}; + member->transitionToOwnedObj(); } -BSONObj sortKey(const WorkingSetMember& member) { - return DocumentMetadataFields::serializeSortKey(member.metadata().isSingleElementKey(), - member.metadata().getSortKey()); +void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) { + transitionMemberToOwnedObj(Document{bo}, member); } -double geoDistance(const WorkingSetMember& member) { - return member.metadata().getGeoNearDistance(); +/** + * Moves document metadata fields from the WSM into the given document 'doc', and returns the same + * document but with populated metadata. + */ +auto attachMetadataToDocument(Document&& doc, WorkingSetMember* member) { + MutableDocument md{std::move(doc)}; + md.setMetadata(member->releaseMetadata()); + return md.freeze(); } -Value geoPoint(const WorkingSetMember& member) { - return member.metadata().getGeoNearPoint(); +/** + * Moves document metadata fields from the document 'doc' into the WSM, and returns the same + * document but without metadata. + */ +auto attachMetadataToWorkingSetMember(Document&& doc, WorkingSetMember* member) { + MutableDocument md{std::move(doc)}; + member->setMetadata(md.releaseMetadata()); + return md.freeze(); } -double textScore(const WorkingSetMember& member) { - auto&& metadata = member.metadata(); - if (metadata.hasTextScore()) { - return metadata.getTextScore(); - } else { - // It is permitted to request a text score when none has been computed. Zero is returned as - // an empty value in this case. - return 0.0; +/** + * Given an index key 'dehyratedKey' with no field names, returns a new Document representing the + * index key after adding field names according to 'keyPattern'. + * + * For example, given: + * - the 'keyPatern' of {'a.b': 1, c: 1} + * - the 'dehydratedKey' of {'': 'abc', '': 10} + * + * The resulting document will be: {a: {b: 'abc'}, c: 10} + */ +auto rehydrateIndexKey(const BSONObj& keyPattern, const BSONObj& dehydratedKey) { + MutableDocument md; + BSONObjIterator keyIter{keyPattern}; + BSONObjIterator valueIter{dehydratedKey}; + + while (keyIter.more() && valueIter.more()) { + auto fieldName = keyIter.next().fieldNameStringData(); + auto value = valueIter.next(); + + // Skip the $** index virtual field, as it's not part of the actual index key. + if (fieldName == "$_path") { + continue; + } + + md.setNestedField(fieldName, Value{value}); } -} -void transitionMemberToOwnedObj(const BSONObj& bo, WorkingSetMember* member) { - member->keyData.clear(); - member->recordId = RecordId(); - member->resetDocument(SnapshotId(), bo); - member->transitionToOwnedObj(); -} + invariant(!keyIter.more()); + invariant(!valueIter.more()); -StatusWith<BSONObj> provideMetaFieldsAndPerformExec(const ProjectionExec& exec, - const WorkingSetMember& member) { - if (exec.needsGeoNearDistance() && !member.metadata().hasGeoNearDistance()) - return Status(ErrorCodes::InternalError, "near loc dist requested but no data available"); - - if (exec.needsGeoNearPoint() && !member.metadata().hasGeoNearPoint()) - return Status(ErrorCodes::InternalError, "near loc proj requested but no data available"); - - return member.hasObj() - ? exec.project(member.doc.value().toBson(), - exec.needsGeoNearDistance() - ? boost::optional<const double>(geoDistance(member)) - : boost::none, - exec.needsGeoNearPoint() ? geoPoint(member) : Value{}, - 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) : Value{}, - exec.needsSortKey() ? sortKey(member) : BSONObj(), - exec.needsTextScore() ? boost::optional<const double>(textScore(member)) - : boost::none, - member.recordId.repr()); + return md.freeze(); } } // namespace @@ -191,26 +191,52 @@ std::unique_ptr<PlanStageStats> ProjectionStage::getStats() { return ret; } -ProjectionStageDefault::ProjectionStageDefault(OperationContext* opCtx, +ProjectionStageDefault::ProjectionStageDefault(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& projObj, + const projection_ast::Projection* projection, WorkingSet* ws, - std::unique_ptr<PlanStage> child, - const MatchExpression& fullExpression, - const CollatorInterface* collator) - : ProjectionStage(opCtx, projObj, ws, std::move(child), "PROJECTION_DEFAULT"), - _exec(opCtx, projObj, &fullExpression, collator) {} + std::unique_ptr<PlanStage> child) + : ProjectionStage{expCtx->opCtx, projObj, ws, std::move(child), "PROJECTION_DEFAULT"}, + _wantRecordId{projection->metadataDeps()[DocumentMetadataFields::kRecordId]}, + _projectType{projection->type()}, + _executor{projection_executor::buildProjectionExecutor(expCtx, projection, {})} {} Status ProjectionStageDefault::transform(WorkingSetMember* member) const { - // The default no-fast-path case. - if (_exec.needsSortKey() && !member->metadata().hasSortKey()) - return Status(ErrorCodes::InternalError, - "sortKey meta-projection requested but no data available"); + Document input; + + // Most metadata should have already been stored within the WSM when we project out a document. + // The recordId metadata is different though, because it's a fundamental part of the WSM and + // we store it within the WSM itself rather than WSM metadata, so we need to transfer it into + // the metadata object if the projection has a recordId $meta expression. + if (_wantRecordId && !member->metadata().hasRecordId()) { + member->metadata().setRecordId(member->recordId); + } - auto projected = provideMetaFieldsAndPerformExec(_exec, *member); - if (!projected.isOK()) - return projected.getStatus(); + if (member->hasObj()) { + input = std::move(member->doc.value()); + } else { + // We have a covered projection, which is only supported in inclusion mode. + invariant(_projectType == projection_ast::ProjectType::kInclusion); + // We're pulling data from an index key, so there must be exactly one key entry in the WSM + // as the planner guarantees that it will never generate a covered plan in the case of index + // intersection. + invariant(member->keyData.size() == 1); + + // For covered projection we will rehydrate in index key into a Document and then pass it + // through the projection executor to include only required fields, including metadata + // fields. + input = rehydrateIndexKey(member->keyData[0].indexKeyPattern, member->keyData[0].keyData); + } - transitionMemberToOwnedObj(projected.getValue(), member); + // Before applying the projection we will move document metadata from the WSM into the document + // itself, in case the projection contains $meta expressions and needs this data, and will move + // it back to the WSM once the projection has been applied. + auto projected = attachMetadataToWorkingSetMember( + _executor->applyTransformation(attachMetadataToDocument(std::move(input), member)), member); + // An exclusion projection can return an unowned object since the output document is + // constructed from the input one backed by BSON which is owned by the storage system, so we + // need to make sure we transition an owned document. + transitionMemberToOwnedObj(projected.getOwned(), member); return Status::OK(); } diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h index e4ad2ef8af6..5b368750cad 100644 --- a/src/mongo/db/exec/projection.h +++ b/src/mongo/db/exec/projection.h @@ -30,15 +30,13 @@ #pragma once #include "mongo/db/exec/plan_stage.h" -#include "mongo/db/exec/projection_exec.h" +#include "mongo/db/exec/projection_executor.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" +#include "mongo/db/query/projection_ast.h" #include "mongo/db/record_id.h" namespace mongo { - -class CollatorInterface; - /** * This stage computes a projection. This is an abstract base class for various projection * implementations. @@ -101,12 +99,11 @@ public: /** * ProjectionNodeDefault should use this for construction. */ - ProjectionStageDefault(OperationContext* opCtx, + ProjectionStageDefault(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& projObj, + const projection_ast::Projection* projection, WorkingSet* ws, - std::unique_ptr<PlanStage> child, - const MatchExpression& fullExpression, - const CollatorInterface* collator); + std::unique_ptr<PlanStage> child); StageType stageType() const final { return STAGE_PROJECTION_DEFAULT; @@ -115,8 +112,10 @@ public: private: Status transform(WorkingSetMember* member) const final; - // Fully-general heavy execution object. - ProjectionExec _exec; + // True, if the projection contains a recordId $meta expression. + const bool _wantRecordId; + const projection_ast::ProjectType _projectType; + std::unique_ptr<parsed_aggregation_projection::ParsedAggregationProjection> _executor; }; /** diff --git a/src/mongo/db/exec/projection_executor.cpp b/src/mongo/db/exec/projection_executor.cpp index 91c56032cd6..29192f19547 100644 --- a/src/mongo/db/exec/projection_executor.cpp +++ b/src/mongo/db/exec/projection_executor.cpp @@ -97,7 +97,7 @@ auto makeProjectionPostImageExpression(const ProjectionExecutorVisitorData<Execu * a field path expressions will be created to access a projection post-image document. */ template <typename Executor> -auto createFindPositionalExpression(projection_ast::ProjectionPositionalASTNode* node, +auto createFindPositionalExpression(const projection_ast::ProjectionPositionalASTNode* node, const ProjectionExecutorVisitorData<Executor>& data, const FieldPath& path) { invariant(node); @@ -123,7 +123,7 @@ auto createFindPositionalExpression(projection_ast::ProjectionPositionalASTNode* * a field path expressions will be created to access a projection post-image document. */ template <typename Executor> -auto createFindSliceExpression(projection_ast::ProjectionSliceASTNode* node, +auto createFindSliceExpression(const projection_ast::ProjectionSliceASTNode* node, const ProjectionExecutorVisitorData<Executor>& data, const FieldPath& path) { invariant(node); @@ -137,7 +137,7 @@ auto createFindSliceExpression(projection_ast::ProjectionSliceASTNode* node, * 'path' on the input document. */ template <typename Executor> -auto createFindElemMatchExpression(projection_ast::ProjectionElemMatchASTNode* node, +auto createFindElemMatchExpression(const projection_ast::ProjectionElemMatchASTNode* node, const ProjectionExecutorVisitorData<Executor>& data, const FieldPath& path) { invariant(node); @@ -166,14 +166,14 @@ auto createFindElemMatchExpression(projection_ast::ProjectionElemMatchASTNode* n * 'PathTrackingVisitorContext'. */ template <typename Executor> -class ProjectionExecutorVisitor final : public projection_ast::ProjectionASTVisitor { +class ProjectionExecutorVisitor final : public projection_ast::ProjectionASTConstVisitor { public: ProjectionExecutorVisitor(ProjectionExecutorVisitorContext<Executor>* context) : _context{context} { invariant(_context); } - void visit(projection_ast::ProjectionPositionalASTNode* node) final { + void visit(const projection_ast::ProjectionPositionalASTNode* node) final { constexpr auto isInclusion = std::is_same_v<Executor, ParsedInclusionProjection>; invariant(isInclusion); @@ -184,7 +184,7 @@ public: userData.setRootReplacementExpression(createFindPositionalExpression(node, userData, path)); } - void visit(projection_ast::ProjectionSliceASTNode* node) final { + void visit(const projection_ast::ProjectionSliceASTNode* node) final { const auto& path = _context->fullPath(); auto& userData = _context->data(); @@ -198,7 +198,7 @@ public: userData.setRootReplacementExpression(createFindSliceExpression(node, userData, path)); } - void visit(projection_ast::ProjectionElemMatchASTNode* node) final { + void visit(const projection_ast::ProjectionElemMatchASTNode* node) final { const auto& path = _context->fullPath(); const auto& userData = _context->data(); @@ -206,14 +206,14 @@ public: path.fullPath(), createFindElemMatchExpression(node, userData, path)); } - void visit(projection_ast::ExpressionASTNode* node) final { + void visit(const projection_ast::ExpressionASTNode* node) final { const auto& path = _context->fullPath(); const auto& userData = _context->data(); userData.rootNode()->addExpressionForPath(path.fullPath(), node->expression()); } - void visit(projection_ast::BooleanConstantASTNode* node) final { + void visit(const projection_ast::BooleanConstantASTNode* node) final { const auto& path = _context->fullPath(); const auto& userData = _context->data(); @@ -231,8 +231,8 @@ public: userData.rootNode()->addProjectionForPath(path.fullPath()); } - void visit(projection_ast::ProjectionPathASTNode* node) final {} - void visit(projection_ast::MatchExpressionASTNode* node) final {} + void visit(const projection_ast::ProjectionPathASTNode* node) final {} + void visit(const projection_ast::MatchExpressionASTNode* node) final {} private: ProjectionExecutorVisitorContext<Executor>* _context; @@ -245,7 +245,7 @@ private: */ template <typename Executor> auto buildProjectionExecutor(boost::intrusive_ptr<ExpressionContext> expCtx, - projection_ast::ProjectionPathASTNode* root, + const projection_ast::ProjectionPathASTNode* root, const ProjectionPolicies policies) { ProjectionExecutorVisitorContext<Executor> context{ {std::make_unique<Executor>(expCtx, policies), expCtx}}; @@ -258,7 +258,7 @@ auto buildProjectionExecutor(boost::intrusive_ptr<ExpressionContext> expCtx, std::unique_ptr<ParsedAggregationProjection> buildProjectionExecutor( boost::intrusive_ptr<ExpressionContext> expCtx, - projection_ast::Projection* projection, + const projection_ast::Projection* projection, const ProjectionPolicies policies) { invariant(projection); diff --git a/src/mongo/db/exec/projection_executor.h b/src/mongo/db/exec/projection_executor.h index 98e7fe89bfd..e3b7b2b24dc 100644 --- a/src/mongo/db/exec/projection_executor.h +++ b/src/mongo/db/exec/projection_executor.h @@ -40,6 +40,6 @@ namespace mongo::projection_executor { */ std::unique_ptr<parsed_aggregation_projection::ParsedAggregationProjection> buildProjectionExecutor( boost::intrusive_ptr<ExpressionContext> expCtx, - projection_ast::Projection* projection, + const projection_ast::Projection* projection, ProjectionPolicies policies); } // namespace mongo::projection_executor diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h index 39ed4e927f5..317544c4944 100644 --- a/src/mongo/db/query/canonical_query.h +++ b/src/mongo/db/query/canonical_query.h @@ -128,6 +128,11 @@ public: const projection_ast::Projection* getProj() const { return _proj.get_ptr(); } + + projection_ast::Projection* getProj() { + return _proj.get_ptr(); + } + const CollatorInterface* getCollator() const { return _collator.get(); } diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 29cc73c097a..dec282568dc 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -417,15 +417,14 @@ StatusWith<PrepareExecutionResult> prepareExecution(OperationContext* opCtx, // Stuff the right data into the params depending on what proj impl we use. if (!canonicalQuery->getProj()->isSimple()) { root = std::make_unique<ProjectionStageDefault>( - opCtx, - canonicalQuery->getProj()->getProjObj(), + canonicalQuery->getExpCtx(), + canonicalQuery->getQueryRequest().getProj(), + canonicalQuery->getProj(), ws, - std::move(root), - *canonicalQuery->root(), - canonicalQuery->getCollator()); + std::move(root)); } else { root = std::make_unique<ProjectionStageSimple>( - opCtx, canonicalQuery->getProj()->getProjObj(), ws, std::move(root)); + opCtx, canonicalQuery->getQueryRequest().getProj(), ws, std::move(root)); } } @@ -683,12 +682,8 @@ StatusWith<unique_ptr<PlanStage>> applyProjection(OperationContext* opCtx, "Cannot use a $meta sortKey projection in findAndModify commands."}; } - return {std::make_unique<ProjectionStageDefault>(opCtx, - projObj, - ws, - std::unique_ptr<PlanStage>(root.release()), - *cq->root(), - cq->getCollator())}; + return {std::make_unique<ProjectionStageDefault>( + cq->getExpCtx(), projObj, &proj, ws, std::unique_ptr<PlanStage>(root.release()))}; } } // namespace diff --git a/src/mongo/db/query/projection.cpp b/src/mongo/db/query/projection.cpp index d2a4ee89264..cd6f83091b2 100644 --- a/src/mongo/db/query/projection.cpp +++ b/src/mongo/db/query/projection.cpp @@ -58,32 +58,32 @@ struct DepsAnalysisData { * Does "broad" analysis on the projection, about whether the entire document, or details from the * match expression are needed and so on. */ -class ProjectionAnalysisVisitor final : public ProjectionASTVisitor { +class ProjectionAnalysisVisitor final : public ProjectionASTConstVisitor { public: ProjectionAnalysisVisitor(ProjectionDependencies* deps) : _deps(deps) { invariant(_deps); } - void visit(ProjectionPathASTNode* node) final { + void visit(const ProjectionPathASTNode* node) final { if (node->parent()) { _deps->hasDottedPath = true; } } - void visit(ProjectionPositionalASTNode* node) final { + void visit(const ProjectionPositionalASTNode* node) final { _deps->requiresMatchDetails = true; _deps->requiresDocument = true; } - void visit(ProjectionSliceASTNode* node) final { + void visit(const ProjectionSliceASTNode* node) final { _deps->requiresDocument = true; } - void visit(ProjectionElemMatchASTNode* node) final { + void visit(const ProjectionElemMatchASTNode* node) final { _deps->requiresDocument = true; } - void visit(ExpressionASTNode* node) final { + void visit(const ExpressionASTNode* node) final { const Expression* expr = node->expressionRaw(); const ExpressionMeta* meta = dynamic_cast<const ExpressionMeta*>(expr); @@ -94,8 +94,8 @@ public: } } - void visit(BooleanConstantASTNode* node) final {} - void visit(MatchExpressionASTNode* node) final {} + void visit(const BooleanConstantASTNode* node) final {} + void visit(const MatchExpressionASTNode* node) final {} private: ProjectionDependencies* _deps; @@ -108,33 +108,33 @@ private: * 'PathTrackingWalker' which will help to maintain the current path via * 'PathTrackingVisitorContext'. */ -class DepsAnalysisVisitor final : public ProjectionASTVisitor { +class DepsAnalysisVisitor final : public ProjectionASTConstVisitor { public: DepsAnalysisVisitor(PathTrackingVisitorContext<DepsAnalysisData>* context) : _context{context} { invariant(_context); } - void visit(MatchExpressionASTNode* node) final { + void visit(const MatchExpressionASTNode* node) final { node->matchExpression()->addDependencies(&_context->data().fieldDependencyTracker); } - void visit(ProjectionPositionalASTNode* node) final { + void visit(const ProjectionPositionalASTNode* node) final { // Positional projection on a.b.c.$ may actually modify a, a.b, a.b.c, etc. // Treat the top-level field as a dependency. addTopLevelPathAsDependency(); } - void visit(ProjectionSliceASTNode* node) final { + void visit(const ProjectionSliceASTNode* node) final { // find() $slice on a.b.c may modify a, a.b, and a.b.c if they're all arrays. // Treat the top-level field as a dependency. addTopLevelPathAsDependency(); } - void visit(ProjectionElemMatchASTNode* node) final { + void visit(const ProjectionElemMatchASTNode* node) final { addFullPathAsDependency(); } - void visit(ExpressionASTNode* node) final { + void visit(const ExpressionASTNode* node) final { // The output of an expression on a dotted path depends on whether that field is an array. invariant(node->parent()); if (!node->parent()->isRoot()) { @@ -144,14 +144,14 @@ public: node->expression()->addDependencies(&_context->data().fieldDependencyTracker); } - void visit(BooleanConstantASTNode* node) final { + void visit(const BooleanConstantASTNode* node) final { // For inclusions, we depend on the field. if (node->value()) { addFullPathAsDependency(); } } - void visit(ProjectionPathASTNode* node) final {} + void visit(const ProjectionPathASTNode* node) final {} private: void addTopLevelPathAsDependency() { @@ -169,7 +169,7 @@ private: PathTrackingVisitorContext<DepsAnalysisData>* _context; }; -auto analyzeProjection(ProjectionPathASTNode* root, ProjectType type) { +auto analyzeProjection(const ProjectionPathASTNode* root, ProjectType type) { ProjectionDependencies deps; PathTrackingVisitorContext<DepsAnalysisData> context; DepsAnalysisVisitor depsAnalysisVisitor{&context}; @@ -194,8 +194,8 @@ auto analyzeProjection(ProjectionPathASTNode* root, ProjectType type) { } // namespace -Projection::Projection(ProjectionPathASTNode root, ProjectType type, const BSONObj& bson) - : _root(std::move(root)), _type(type), _deps(analyzeProjection(&_root, type)), _bson(bson) {} +Projection::Projection(ProjectionPathASTNode root, ProjectType type) + : _root(std::move(root)), _type(type), _deps(analyzeProjection(&_root, type)) {} namespace { diff --git a/src/mongo/db/query/projection.h b/src/mongo/db/query/projection.h index 91afe2b931f..486f9ed9abf 100644 --- a/src/mongo/db/query/projection.h +++ b/src/mongo/db/query/projection.h @@ -59,7 +59,11 @@ struct ProjectionDependencies { enum class ProjectType { kInclusion, kExclusion }; class Projection { public: - Projection(ProjectionPathASTNode root, ProjectType type, const BSONObj& bson); + Projection(ProjectionPathASTNode root, ProjectType type); + + const ProjectionPathASTNode* root() const { + return &_root; + } ProjectionPathASTNode* root() { return &_root; @@ -105,15 +109,6 @@ public: bool isFieldRetainedExactly(StringData path); /** - * TODO SERVER-42423: Delete this method and _bson. - * - * This method is deprecated and new call sites should not be added. - */ - BSONObj getProjObj() const { - return _bson; - } - - /** * A projection is considered "simple" if it doesn't require the full document, operates only * on top-level fields, has no positional projection, and doesn't require the sort key. */ @@ -125,11 +120,7 @@ public: private: ProjectionPathASTNode _root; ProjectType _type; - ProjectionDependencies _deps; - - // Do NOT add new usages of this. - BSONObj _bson; }; } // namespace projection_ast diff --git a/src/mongo/db/query/projection_ast.h b/src/mongo/db/query/projection_ast.h index 2c136ba0a00..8a279fbbf8c 100644 --- a/src/mongo/db/query/projection_ast.h +++ b/src/mongo/db/query/projection_ast.h @@ -67,7 +67,8 @@ public: virtual std::unique_ptr<ASTNode> clone() const = 0; - virtual void acceptVisitor(ProjectionASTVisitor* visitor) = 0; + virtual void acceptVisitor(ProjectionASTMutableVisitor* visitor) = 0; + virtual void acceptVisitor(ProjectionASTConstVisitor* visitor) const = 0; const ASTNodeVector& children() const { return _children; @@ -103,7 +104,11 @@ public: return std::make_unique<MatchExpressionASTNode>(*this); } - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -124,7 +129,11 @@ public: invariant(_children.size() == _fieldNames.size()); } - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -163,7 +172,11 @@ public: addChildToInternalVector(std::move(child)); } - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -176,7 +189,11 @@ class ProjectionSliceASTNode final : public ASTNode { public: ProjectionSliceASTNode(boost::optional<int> skip, int limit) : _skip(skip), _limit(limit) {} - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -204,7 +221,11 @@ public: addChildToInternalVector(std::move(child)); } - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -228,7 +249,11 @@ public: _expr = clonedExpr; } - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } @@ -252,7 +277,11 @@ class BooleanConstantASTNode final : public ASTNode { public: BooleanConstantASTNode(bool val) : _val(val) {} - void acceptVisitor(ProjectionASTVisitor* visitor) override { + void acceptVisitor(ProjectionASTMutableVisitor* visitor) override { + visitor->visit(this); + } + + void acceptVisitor(ProjectionASTConstVisitor* visitor) const override { visitor->visit(this); } diff --git a/src/mongo/db/query/projection_ast_path_tracking_visitor.h b/src/mongo/db/query/projection_ast_path_tracking_visitor.h index 2108233ac8e..affc3732dd6 100644 --- a/src/mongo/db/query/projection_ast_path_tracking_visitor.h +++ b/src/mongo/db/query/projection_ast_path_tracking_visitor.h @@ -106,14 +106,14 @@ namespace { * This is intended to be used with the 'ProjectionPathTrackingWalker' only to correctly maintain * the state about the current path being visited. */ -template <class UserData = PathTrackingDummyDefaultType> -class PathTrackingPreVisitor final : public ProjectionASTVisitor { +template <class UserData = PathTrackingDummyDefaultType, bool IsConst = true> +class PathTrackingPreVisitor final : public ProjectionASTVisitor<IsConst> { public: PathTrackingPreVisitor(PathTrackingVisitorContext<UserData>* context) : _context{context} { invariant(_context); } - void visit(ProjectionPathASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ProjectionPathASTNode> node) final { if (node->parent()) { _context->setBasePath(_context->fullPath()); _context->popFrontFieldName(); @@ -122,12 +122,12 @@ public: _context->pushFieldNames({node->fieldNames().begin(), node->fieldNames().end()}); } - void visit(MatchExpressionASTNode* node) final {} - void visit(ProjectionPositionalASTNode* node) final {} - void visit(ProjectionSliceASTNode* node) final {} - void visit(ProjectionElemMatchASTNode* node) final {} - void visit(ExpressionASTNode* node) final {} - void visit(BooleanConstantASTNode* node) final {} + void visit(MaybeConstPtr<IsConst, MatchExpressionASTNode> node) final {} + void visit(MaybeConstPtr<IsConst, ProjectionPositionalASTNode> node) final {} + void visit(MaybeConstPtr<IsConst, ProjectionSliceASTNode> node) final {} + void visit(MaybeConstPtr<IsConst, ProjectionElemMatchASTNode> node) final {} + void visit(MaybeConstPtr<IsConst, ExpressionASTNode> node) final {} + void visit(MaybeConstPtr<IsConst, BooleanConstantASTNode> node) final {} private: PathTrackingVisitorContext<UserData>* _context; @@ -139,14 +139,14 @@ private: * This is intended to be used with the 'PathTrackingWalker' only to correctly maintain the state * about the current path being visited. */ -template <class UserData = PathTrackingDummyDefaultType> -class PathTrackingPostVisitor final : public ProjectionASTVisitor { +template <class UserData = PathTrackingDummyDefaultType, bool IsConst = true> +class PathTrackingPostVisitor final : public ProjectionASTVisitor<IsConst> { public: PathTrackingPostVisitor(PathTrackingVisitorContext<UserData>* context) : _context{context} { invariant(_context); } - void visit(projection_ast::ProjectionPathASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ProjectionPathASTNode> node) final { _context->popFieldNames(); if (_context->basePath()) { @@ -161,27 +161,27 @@ public: } } - void visit(ProjectionPositionalASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ProjectionPositionalASTNode> node) final { _context->popFrontFieldName(); } - void visit(ProjectionSliceASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ProjectionSliceASTNode> node) final { _context->popFrontFieldName(); } - void visit(ProjectionElemMatchASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ProjectionElemMatchASTNode> node) final { _context->popFrontFieldName(); } - void visit(ExpressionASTNode* node) final { + void visit(MaybeConstPtr<IsConst, ExpressionASTNode> node) final { _context->popFrontFieldName(); } - void visit(BooleanConstantASTNode* node) final { + void visit(MaybeConstPtr<IsConst, BooleanConstantASTNode> node) final { _context->popFrontFieldName(); } - void visit(MatchExpressionASTNode* node) final {} + void visit(MaybeConstPtr<IsConst, MatchExpressionASTNode> node) final {} private: PathTrackingVisitorContext<UserData>* _context; @@ -197,12 +197,12 @@ private: * The visitors specified in the 'preVisitors' and 'postVisitors' parameters will be visited in * the same order as they were added to the vector. */ -template <class UserData = PathTrackingDummyDefaultType> +template <class UserData = PathTrackingDummyDefaultType, bool IsConst = true> class PathTrackingWalker final { public: PathTrackingWalker(PathTrackingVisitorContext<UserData>* context, - std::vector<ProjectionASTVisitor*> preVisitors, - std::vector<ProjectionASTVisitor*> postVisitors) + std::vector<ProjectionASTVisitor<IsConst>*> preVisitors, + std::vector<ProjectionASTVisitor<IsConst>*> postVisitors) : _pathTrackingPreVisitor{context}, _pathTrackingPostVisitor{context}, _preVisitors{std::move(preVisitors)}, @@ -211,25 +211,25 @@ public: _postVisitors.push_back(&_pathTrackingPostVisitor); } - void preVisit(projection_ast::ASTNode* node) { + void preVisit(MaybeConstPtr<IsConst, projection_ast::ASTNode> node) { for (auto visitor : _preVisitors) { node->acceptVisitor(visitor); } } - void postVisit(projection_ast::ASTNode* node) { + void postVisit(MaybeConstPtr<IsConst, projection_ast::ASTNode> node) { for (auto visitor : _postVisitors) { node->acceptVisitor(visitor); } } - void inVisit(long count, projection_ast::ASTNode* node) {} + void inVisit(long count, MaybeConstPtr<IsConst, ASTNode> node) {} private: - PathTrackingPreVisitor<UserData> _pathTrackingPreVisitor; - PathTrackingPostVisitor<UserData> _pathTrackingPostVisitor; - std::vector<ProjectionASTVisitor*> _preVisitors; - std::vector<ProjectionASTVisitor*> _postVisitors; + PathTrackingPreVisitor<UserData, IsConst> _pathTrackingPreVisitor; + PathTrackingPostVisitor<UserData, IsConst> _pathTrackingPostVisitor; + std::vector<ProjectionASTVisitor<IsConst>*> _preVisitors; + std::vector<ProjectionASTVisitor<IsConst>*> _postVisitors; }; } // namespace projection_ast } // namespace mongo diff --git a/src/mongo/db/query/projection_ast_util.cpp b/src/mongo/db/query/projection_ast_util.cpp index f2b4c2aa1ee..1d31ddef614 100644 --- a/src/mongo/db/query/projection_ast_util.cpp +++ b/src/mongo/db/query/projection_ast_util.cpp @@ -44,16 +44,17 @@ struct BSONVisitorContext { } }; -class BSONPreVisitor : public ProjectionASTVisitor { +class BSONPreVisitor : public ProjectionASTConstVisitor { public: BSONPreVisitor(BSONVisitorContext* context) : _context(context) {} - virtual void visit(MatchExpressionASTNode* node) { + virtual void visit(const MatchExpressionASTNode* node) { static_cast<const MatchExpressionASTNode*>(node)->matchExpression()->serialize( &_context->builder()); _context->fieldNames.top().pop_front(); } - virtual void visit(ProjectionPathASTNode* node) { + + virtual void visit(const ProjectionPathASTNode* node) { if (!node->parent()) { // No root of the tree, thus this node has no field name. _context->builders.push(BSONObjBuilder()); @@ -65,7 +66,8 @@ public: _context->fieldNames.push( std::list<std::string>(node->fieldNames().begin(), node->fieldNames().end())); } - virtual void visit(ProjectionPositionalASTNode* node) { + + virtual void visit(const ProjectionPositionalASTNode* node) { // ProjectionPositional always has the original query's match expression node as its // child. Serialize as: {"positional.projection.field.$": <original match expression>}. _context->builders.push(_context->builder().subobjStart(getFieldName() + ".$")); @@ -74,7 +76,8 @@ public: // been put on the stack (just like every other node), and will pop it. _context->fieldNames.push({"<dummy>"}); } - virtual void visit(ProjectionSliceASTNode* node) { + + virtual void visit(const ProjectionSliceASTNode* node) { BSONObjBuilder sub(_context->builder().subobjStart(getFieldName())); if (node->skip()) { sub.appendArray("$slice", BSON_ARRAY(*node->skip() << node->limit())); @@ -82,13 +85,16 @@ public: sub.appendNumber("$slice", node->limit()); } } - virtual void visit(ProjectionElemMatchASTNode* node) { + + virtual void visit(const ProjectionElemMatchASTNode* node) { // Defer to the child, match expression node. } - virtual void visit(ExpressionASTNode* node) { + + virtual void visit(const ExpressionASTNode* node) { node->expression()->serialize(false).addToBsonObj(&_context->builder(), getFieldName()); } - virtual void visit(BooleanConstantASTNode* node) { + + virtual void visit(const BooleanConstantASTNode* node) { _context->builders.top().append(getFieldName(), node->value()); } @@ -104,12 +110,11 @@ private: BSONVisitorContext* _context; }; -class BSONPostVisitor : public ProjectionASTVisitor { +class BSONPostVisitor : public ProjectionASTConstVisitor { public: BSONPostVisitor(BSONVisitorContext* context) : _context(context) {} - virtual void visit(MatchExpressionASTNode* node) {} - virtual void visit(ProjectionPathASTNode* node) { + virtual void visit(const ProjectionPathASTNode* node) { // Don't pop the top builder. if (node->parent()) { // Pop the BSONObjBuilder that was added in the pre visitor. @@ -120,15 +125,17 @@ public: invariant(_context->fieldNames.top().empty()); _context->fieldNames.pop(); } - virtual void visit(ProjectionPositionalASTNode* node) { + + virtual void visit(const ProjectionPositionalASTNode* node) { _context->builders.pop(); _context->fieldNames.pop(); } - virtual void visit(ProjectionSliceASTNode* node) {} - virtual void visit(ProjectionElemMatchASTNode* node) {} - virtual void visit(ExpressionASTNode* node) {} - virtual void visit(BooleanConstantASTNode* node) {} + virtual void visit(const MatchExpressionASTNode* node) {} + virtual void visit(const ProjectionSliceASTNode* node) {} + virtual void visit(const ProjectionElemMatchASTNode* node) {} + virtual void visit(const ExpressionASTNode* node) {} + virtual void visit(const BooleanConstantASTNode* node) {} private: BSONVisitorContext* _context; @@ -138,15 +145,15 @@ class BSONWalker { public: BSONWalker() : _preVisitor(&_context), _postVisitor(&_context) {} - void preVisit(ASTNode* node) { + void preVisit(const ASTNode* node) { node->acceptVisitor(&_preVisitor); } - void postVisit(ASTNode* node) { + void postVisit(const ASTNode* node) { node->acceptVisitor(&_postVisitor); } - void inVisit(long count, ASTNode* node) { + void inVisit(long count, const ASTNode* node) { // No op. } @@ -165,7 +172,7 @@ private: }; } // namespace -BSONObj astToDebugBSON(ASTNode* root) { +BSONObj astToDebugBSON(const ASTNode* root) { BSONWalker walker; projection_ast_walker::walk(&walker, root); diff --git a/src/mongo/db/query/projection_ast_util.h b/src/mongo/db/query/projection_ast_util.h index 231f037b48c..af89254a9a1 100644 --- a/src/mongo/db/query/projection_ast_util.h +++ b/src/mongo/db/query/projection_ast_util.h @@ -36,6 +36,6 @@ namespace projection_ast { /** * This is intended to be used for debug output, not for serialization. */ -BSONObj astToDebugBSON(ASTNode* root); +BSONObj astToDebugBSON(const ASTNode* root); } // namespace projection_ast } // namespace mongo diff --git a/src/mongo/db/query/projection_ast_visitor.h b/src/mongo/db/query/projection_ast_visitor.h index 39caedac3d8..81cc07bf932 100644 --- a/src/mongo/db/query/projection_ast_visitor.h +++ b/src/mongo/db/query/projection_ast_visitor.h @@ -31,7 +31,6 @@ namespace mongo { namespace projection_ast { - class MatchExpressionASTNode; class ProjectionPathASTNode; class ProjectionPositionalASTNode; @@ -41,20 +40,34 @@ class ExpressionASTNode; class BooleanConstantASTNode; /** + * A template type which resolves to 'const T*' if 'IsConst' argument is 'true', and to 'T*' + * otherwise. + */ +template <bool IsConst, typename T> +using MaybeConstPtr = typename std::conditional<IsConst, const T*, T*>::type; + +/** * Visitor pattern for ProjectionAST. * * This code is not responsible for traversing the AST, only for performing the double-dispatch. + * + * If the visitor doesn't intend to modify the AST, then the template argument 'IsConst' should be + * set to 'true'. In this case all 'visit()' methods will take a const pointer to a visiting node. */ +template <bool IsConst = false> class ProjectionASTVisitor { public: virtual ~ProjectionASTVisitor() = default; - virtual void visit(MatchExpressionASTNode* node) = 0; - virtual void visit(ProjectionPathASTNode* node) = 0; - virtual void visit(ProjectionPositionalASTNode* node) = 0; - virtual void visit(ProjectionSliceASTNode* node) = 0; - virtual void visit(ProjectionElemMatchASTNode* node) = 0; - virtual void visit(ExpressionASTNode* node) = 0; - virtual void visit(BooleanConstantASTNode* node) = 0; + virtual void visit(MaybeConstPtr<IsConst, MatchExpressionASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, ProjectionPathASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, ProjectionPositionalASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, ProjectionSliceASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, ProjectionElemMatchASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, ExpressionASTNode> node) = 0; + virtual void visit(MaybeConstPtr<IsConst, BooleanConstantASTNode> node) = 0; }; + +using ProjectionASTMutableVisitor = ProjectionASTVisitor<false>; +using ProjectionASTConstVisitor = ProjectionASTVisitor<true>; } // namespace projection_ast } // namespace mongo diff --git a/src/mongo/db/query/projection_ast_walker.h b/src/mongo/db/query/projection_ast_walker.h index 5d697efee7a..64674dbd3d0 100644 --- a/src/mongo/db/query/projection_ast_walker.h +++ b/src/mongo/db/query/projection_ast_walker.h @@ -44,9 +44,12 @@ namespace mongo::projection_ast_walker { * * walker.postVisit() once after walking to each child. * Each of the ASTNode's child ASTNode is recursively walked and the same three methods are * called for it. + * + * If the caller doesn't intend to modify the AST, then the template argument 'IsConst' should be + * set to 'true'. In this case the 'node' pointer will be qualified with 'const'. */ -template <typename Walker> -void walk(Walker* walker, projection_ast::ASTNode* node) { +template <typename Walker, bool IsConst = true> +void walk(Walker* walker, projection_ast::MaybeConstPtr<IsConst, projection_ast::ASTNode> node) { if (node) { walker->preVisit(node); @@ -55,7 +58,7 @@ void walk(Walker* walker, projection_ast::ASTNode* node) { if (count) walker->inVisit(count, node); ++count; - walk(walker, child.get()); + walk<Walker, IsConst>(walker, child.get()); } walker->postVisit(node); diff --git a/src/mongo/db/query/projection_parser.cpp b/src/mongo/db/query/projection_parser.cpp index a64d2e7dbd8..9642b2456d2 100644 --- a/src/mongo/db/query/projection_parser.cpp +++ b/src/mongo/db/query/projection_parser.cpp @@ -347,7 +347,7 @@ Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx, addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(true)); } - return Projection{std::move(root), *type, obj}; + return Projection{std::move(root), *type}; } Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx, diff --git a/src/mongo/db/query/projection_test.cpp b/src/mongo/db/query/projection_test.cpp index c0044173cd4..c4180584f2f 100644 --- a/src/mongo/db/query/projection_test.cpp +++ b/src/mongo/db/query/projection_test.cpp @@ -229,9 +229,7 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjection) { // A projection with just a $meta projection defaults to an exclusion projection. auto proj = createProjection("{}", "{foo: {$meta: 'sortKey'}}"); - ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{foo: {$meta: 'sortKey'}}")); ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); - ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); @@ -241,10 +239,7 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjection) { TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjectionWithOtherFields) { auto proj = createProjection("{}", "{a: 0, foo: {$meta: 'sortKey'}}"); - ASSERT_BSONOBJ_EQ(proj.getProjObj(), fromjson("{a: 0, foo: {$meta: 'sortKey'}}")); ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); - - ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); ASSERT_TRUE(proj.requiresDocument()); @@ -253,9 +248,7 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInExclusionProjectionWithOtherFie 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.metadataDeps()[DocumentMetadataFields::kSortKey]); - ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); @@ -265,9 +258,7 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionInInclusionProjection) { 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.metadataDeps()[DocumentMetadataFields::kSortKey]); - ASSERT_FALSE(proj.requiresDocument()); ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); @@ -277,11 +268,8 @@ TEST(QueryProjectionTest, SortKeyMetaProjectionDoesNotRequireDocument) { TEST(QueryProjectionTest, SortKeyMetaAndSlice) { auto proj = createProjection("{}", "{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$slice: 1}}"); - ASSERT_BSONOBJ_EQ(proj.getProjObj(), - fromjson("{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$slice: 1}}")); ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_TRUE(proj.requiresDocument()); - ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); @@ -291,11 +279,8 @@ TEST(QueryProjectionTest, SortKeyMetaAndElemMatch) { auto proj = createProjection("{}", "{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$elemMatch: {a: 1}}}"); - ASSERT_BSONOBJ_EQ(proj.getProjObj(), - fromjson("{a: 1, foo: {$meta: 'sortKey'}, _id: 0, b: {$elemMatch: {a: 1}}}")); ASSERT_TRUE(proj.metadataDeps()[DocumentMetadataFields::kSortKey]); ASSERT_TRUE(proj.requiresDocument()); - ASSERT_FALSE(proj.requiresMatchDetails()); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearDist]); ASSERT_FALSE(proj.metadataDeps()[DocumentMetadataFields::kGeoNearPoint]); diff --git a/src/mongo/db/query/query_planner_test_lib.cpp b/src/mongo/db/query/query_planner_test_lib.cpp index 23287d59225..a08283aa6de 100644 --- a/src/mongo/db/query/query_planner_test_lib.cpp +++ b/src/mongo/db/query/query_planner_test_lib.cpp @@ -43,6 +43,8 @@ #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_factory_mock.h" +#include "mongo/db/query/projection_ast_util.h" +#include "mongo/db/query/projection_parser.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_solution.h" #include "mongo/unittest/unittest.h" @@ -617,7 +619,13 @@ bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, return false; } - return SimpleBSONObjComparator::kInstance.evaluate(spec.Obj() == pn->proj.getProjObj()) && + // Create an empty/dummy expression context without access to the operation context and + // collator. This should be sufficient to parse a projection. + auto expCtx = make_intrusive<ExpressionContext>(nullptr, nullptr); + auto projection = projection_ast::parse(expCtx, spec.Obj(), {}); + auto specProjObj = projection_ast::astToDebugBSON(projection.root()); + auto solnProjObj = projection_ast::astToDebugBSON(pn->proj.root()); + return SimpleBSONObjComparator::kInstance.evaluate(specProjObj == solnProjObj) && solutionMatches(child.Obj(), pn->children[0], relaxBoundsCheck); } else if (STAGE_SORT == trueSoln->getType()) { const SortNode* sn = static_cast<const SortNode*>(trueSoln); diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp index e09c8c82bd0..245621eb388 100644 --- a/src/mongo/db/query/query_solution.cpp +++ b/src/mongo/db/query/query_solution.cpp @@ -42,6 +42,7 @@ #include "mongo/db/query/index_bounds_builder.h" #include "mongo/db/query/planner_analysis.h" #include "mongo/db/query/planner_wildcard_helpers.h" +#include "mongo/db/query/projection_ast_util.h" #include "mongo/db/query/query_planner_common.h" namespace mongo { @@ -878,7 +879,7 @@ void ProjectionNode::appendToString(str::stream* ss, int indent) const { addIndent(ss, indent); *ss << "PROJ\n"; addIndent(ss, indent + 1); - *ss << "proj = " << proj.getProjObj().toString() << '\n'; + *ss << "proj = " << projection_ast::astToDebugBSON(proj.root()).toString() << '\n'; addIndent(ss, indent + 1); *ss << "type = " << projectionImplementationTypeToString() << '\n'; addCommon(ss, indent); diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp index b70ca49a5a3..a3cc6aee999 100644 --- a/src/mongo/db/query/stage_builder.cpp +++ b/src/mongo/db/query/stage_builder.cpp @@ -145,24 +145,26 @@ std::unique_ptr<PlanStage> buildStages(OperationContext* opCtx, case STAGE_PROJECTION_DEFAULT: { auto pn = static_cast<const ProjectionNodeDefault*>(root); auto childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws); - return std::make_unique<ProjectionStageDefault>(opCtx, - pn->proj.getProjObj(), + return std::make_unique<ProjectionStageDefault>(cq.getExpCtx(), + cq.getQueryRequest().getProj(), + cq.getProj(), ws, - std::move(childStage), - pn->fullExpression, - cq.getCollator()); + std::move(childStage)); } case STAGE_PROJECTION_COVERED: { auto pn = static_cast<const ProjectionNodeCovered*>(root); auto childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws); - return std::make_unique<ProjectionStageCovered>( - opCtx, pn->proj.getProjObj(), ws, std::move(childStage), pn->coveredKeyObj); + return std::make_unique<ProjectionStageCovered>(opCtx, + cq.getQueryRequest().getProj(), + ws, + std::move(childStage), + pn->coveredKeyObj); } case STAGE_PROJECTION_SIMPLE: { auto pn = static_cast<const ProjectionNodeSimple*>(root); auto childStage = buildStages(opCtx, collection, cq, qsol, pn->children[0], ws); return std::make_unique<ProjectionStageSimple>( - opCtx, pn->proj.getProjObj(), ws, std::move(childStage)); + opCtx, cq.getQueryRequest().getProj(), ws, std::move(childStage)); } case STAGE_LIMIT: { const LimitNode* ln = static_cast<const LimitNode*>(root); |