summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/exec/projection_exec_agg.cpp36
-rw-r--r--src/mongo/db/exec/projection_exec_agg.h12
-rw-r--r--src/mongo/db/pipeline/SConscript1
-rw-r--r--src/mongo/db/pipeline/document_source_project.cpp15
-rw-r--r--src/mongo/db/pipeline/parsed_add_fields.cpp20
-rw-r--r--src/mongo/db/pipeline/parsed_add_fields.h18
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.cpp37
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.h65
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp266
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_node.h190
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp192
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection.cpp116
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection.h81
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp118
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.cpp242
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.h155
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp189
17 files changed, 805 insertions, 948 deletions
diff --git a/src/mongo/db/exec/projection_exec_agg.cpp b/src/mongo/db/exec/projection_exec_agg.cpp
index 9f4b6a4c1ea..905d66561e0 100644
--- a/src/mongo/db/exec/projection_exec_agg.cpp
+++ b/src/mongo/db/exec/projection_exec_agg.cpp
@@ -41,7 +41,8 @@ namespace mongo {
class ProjectionExecAgg::ProjectionExecutor {
public:
using ParsedAggregationProjection = parsed_aggregation_projection::ParsedAggregationProjection;
- using ProjectionParseMode = ParsedAggregationProjection::ProjectionParseMode;
+ using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
+
using TransformerType = TransformerInterface::TransformerType;
ProjectionExecutor(BSONObj projSpec,
@@ -52,27 +53,34 @@ public:
// ban computed fields from the projection, the ExpressionContext will never be used.
boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(nullptr, nullptr));
+ // Create a ProjectionPolicies object, to be populated based on the passed arguments.
+ ParsedAggregationProjection::ProjectionPolicies projectionPolicies;
+
// Default projection behaviour is to include _id if the projection spec omits it. If the
// caller has specified that we should *exclude* _id by default, do so here. We translate
- // DefaultIdPolicy to ParsedAggregationProjection::ProjectionDefaultIdPolicy in order to
- // avoid exposing internal aggregation types to the query system.
- ParsedAggregationProjection::ProjectionDefaultIdPolicy idPolicy =
+ // DefaultIdPolicy to ProjectionPolicies::DefaultIdPolicy in order to avoid exposing
+ // internal aggregation types to the query system.
+ projectionPolicies.idPolicy =
(defaultIdPolicy == ProjectionExecAgg::DefaultIdPolicy::kIncludeId
- ? ParsedAggregationProjection::ProjectionDefaultIdPolicy::kIncludeId
- : ParsedAggregationProjection::ProjectionDefaultIdPolicy::kExcludeId);
+ ? ProjectionPolicies::DefaultIdPolicy::kIncludeId
+ : ProjectionPolicies::DefaultIdPolicy::kExcludeId);
// By default, $project will recurse through nested arrays. If the caller has specified that
// it should not, we inhibit it from doing so here. We separate this class' internal enum
- // ArrayRecursionPolicy from ParsedAggregationProjection::ProjectionArrayRecursionPolicy
- // in order to avoid exposing aggregation types to the query system.
- ParsedAggregationProjection::ProjectionArrayRecursionPolicy recursionPolicy =
+ // ArrayRecursionPolicy from ProjectionPolicies::ArrayRecursionPolicy in order to avoid
+ // exposing aggregation types to the query system.
+ projectionPolicies.arrayRecursionPolicy =
(arrayRecursionPolicy == ArrayRecursionPolicy::kRecurseNestedArrays
- ? ParsedAggregationProjection::ProjectionArrayRecursionPolicy::kRecurseNestedArrays
- : ParsedAggregationProjection::ProjectionArrayRecursionPolicy::
- kDoNotRecurseNestedArrays);
+ ? ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays
+ : ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+
+ // Inclusion projections permit computed fields by default, so we must explicitly ban them.
+ // Computed fields are implicitly banned for exclusions.
+ projectionPolicies.computedFieldsPolicy =
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields;
- _projection = ParsedAggregationProjection::create(
- expCtx, projSpec, idPolicy, recursionPolicy, ProjectionParseMode::kBanComputedFields);
+ // Construct a ParsedAggregationProjection for the given projection spec and policies.
+ _projection = ParsedAggregationProjection::create(expCtx, projSpec, projectionPolicies);
// For an inclusion, record the exhaustive set of fields retained by the projection.
if (getType() == ProjectionType::kInclusionProjection) {
diff --git a/src/mongo/db/exec/projection_exec_agg.h b/src/mongo/db/exec/projection_exec_agg.h
index 12ab79c4b31..ea2317ee745 100644
--- a/src/mongo/db/exec/projection_exec_agg.h
+++ b/src/mongo/db/exec/projection_exec_agg.h
@@ -43,12 +43,6 @@ namespace mongo {
*/
class ProjectionExecAgg {
public:
- // Allows the caller to specify how the projection should handle nested arrays; that is, an
- // array whose immediate parent is itself an array. For example, in the case of sample document
- // {a: [1, 2, [3, 4], {b: [5, 6]}]} the array [3, 4] is a nested array. The array [5, 6] is not,
- // because there is an intervening object between it and its closest array ancestor.
- enum class ArrayRecursionPolicy { kRecurseNestedArrays, kDoNotRecurseNestedArrays };
-
// Allows the caller to indicate whether the projection should default to including or excluding
// the _id field in the event that the projection spec does not specify the desired behavior.
// For instance, given a projection {a: 1}, specifying 'kExcludeId' is equivalent to projecting
@@ -57,6 +51,12 @@ public:
// instance, {a: 1, _id: 0} will exclude _id for both 'kExcludeId' and 'kIncludeId'.
enum class DefaultIdPolicy { kIncludeId, kExcludeId };
+ // Allows the caller to specify how the projection should handle nested arrays; that is, an
+ // array whose immediate parent is itself an array. For example, in the case of sample document
+ // {a: [1, 2, [3, 4], {b: [5, 6]}]} the array [3, 4] is a nested array. The array [5, 6] is not,
+ // because there is an intervening object between it and its closest array ancestor.
+ enum class ArrayRecursionPolicy { kRecurseNestedArrays, kDoNotRecurseNestedArrays };
+
enum class ProjectionType { kInclusionProjection, kExclusionProjection };
static std::unique_ptr<ProjectionExecAgg> create(BSONObj projSpec,
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index c236e273752..0717e281ce1 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -495,6 +495,7 @@ env.Library(
target='parsed_aggregation_projection',
source=[
'parsed_aggregation_projection.cpp',
+ 'parsed_aggregation_projection_node.cpp',
'parsed_exclusion_projection.cpp',
'parsed_inclusion_projection.cpp',
'parsed_add_fields.cpp',
diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp
index 940055336a2..593f9e7018b 100644
--- a/src/mongo/db/pipeline/document_source_project.cpp
+++ b/src/mongo/db/pipeline/document_source_project.cpp
@@ -41,10 +41,8 @@
namespace mongo {
using boost::intrusive_ptr;
-using parsed_aggregation_projection::ParsedAggregationProjection;
-
-using ProjectionArrayRecursionPolicy = ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
-using ProjectionDefaultIdPolicy = ParsedAggregationProjection::ProjectionDefaultIdPolicy;
+using ParsedAggregationProjection = parsed_aggregation_projection::ParsedAggregationProjection;
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
REGISTER_DOCUMENT_SOURCE(project,
LiteParsedDocumentSourceDefault::parse,
@@ -55,10 +53,11 @@ intrusive_ptr<DocumentSource> DocumentSourceProject::create(
const bool isIndependentOfAnyCollection = false;
intrusive_ptr<DocumentSource> project(new DocumentSourceSingleDocumentTransformation(
expCtx,
- ParsedAggregationProjection::create(expCtx,
- projectSpec,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays),
+ ParsedAggregationProjection::create(
+ expCtx,
+ projectSpec,
+ {ProjectionPolicies::DefaultIdPolicy::kIncludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays}),
"$project",
isIndependentOfAnyCollection));
return project;
diff --git a/src/mongo/db/pipeline/parsed_add_fields.cpp b/src/mongo/db/pipeline/parsed_add_fields.cpp
index 587b88b71a3..e179b195e91 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.cpp
+++ b/src/mongo/db/pipeline/parsed_add_fields.cpp
@@ -63,7 +63,7 @@ void ParsedAddFields::parse(const BSONObj& spec) {
// The field name might be a dotted path. If so, we need to keep adding children
// to our tree until we create a child that represents that path.
auto remainingPath = FieldPath(elem.fieldName());
- auto child = _root.get();
+ auto* child = _root.get();
while (remainingPath.getPathLength() > 1) {
child = child->addOrGetChild(remainingPath.getFieldName(0).toString());
remainingPath = remainingPath.tail();
@@ -75,7 +75,7 @@ void ParsedAddFields::parse(const BSONObj& spec) {
}
} else {
// This is a literal or regular value.
- _root->addComputedField(
+ _root->addExpressionForPath(
FieldPath(elem.fieldName()),
Expression::parseOperand(_expCtx, elem, _expCtx->variablesParseState));
}
@@ -85,7 +85,7 @@ void ParsedAddFields::parse(const BSONObj& spec) {
Document ParsedAddFields::applyProjection(const Document& inputDoc) const {
// The output doc is the same as the input doc, with the added fields.
MutableDocument output(inputDoc);
- _root->addComputedFields(&output, inputDoc);
+ _root->applyExpressions(inputDoc, &output);
// Pass through the metadata.
output.copyMetaDataFrom(inputDoc);
@@ -96,11 +96,10 @@ bool ParsedAddFields::parseObjectAsExpression(StringData pathToObject,
const BSONObj& objSpec,
const VariablesParseState& variablesParseState) {
if (objSpec.firstElementFieldName()[0] == '$') {
- // This is an expression like {$add: [...]}. We have already verified that it has only one
- // field.
+ // This is an expression like {$add: [...]}. We already verified that it has only one field.
invariant(objSpec.nFields() == 1);
- _root->addComputedField(pathToObject,
- Expression::parseExpression(_expCtx, objSpec, variablesParseState));
+ _root->addExpressionForPath(
+ pathToObject, Expression::parseExpression(_expCtx, objSpec, variablesParseState));
return true;
}
return false;
@@ -123,13 +122,14 @@ void ParsedAddFields::parseSubObject(const BSONObj& subObj,
elem.Obj(),
variablesParseState)) {
// It was a nested subobject
- auto child = node->addOrGetChild(fieldName);
+ auto* child = node->addOrGetChild(fieldName);
parseSubObject(elem.Obj(), variablesParseState, child);
}
} else {
// This is a literal or regular value.
- node->addComputedField(FieldPath(elem.fieldName()),
- Expression::parseOperand(_expCtx, elem, variablesParseState));
+ node->addExpressionForPath(
+ FieldPath(elem.fieldName()),
+ Expression::parseOperand(_expCtx, elem, variablesParseState));
}
}
}
diff --git a/src/mongo/db/pipeline/parsed_add_fields.h b/src/mongo/db/pipeline/parsed_add_fields.h
index be193ffa88f..641fe890b95 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.h
+++ b/src/mongo/db/pipeline/parsed_add_fields.h
@@ -58,10 +58,12 @@ public:
* applicable to the $addFields "projection" stage. We make them non-configurable here.
*/
ParsedAddFields(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ParsedAggregationProjection(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays),
- _root(new InclusionNode(_arrayRecursionPolicy)) {}
+ : ParsedAggregationProjection(
+ expCtx,
+ {ProjectionPolicies::DefaultIdPolicy::kIncludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays,
+ ProjectionPolicies::ComputedFieldsPolicy::kAllowComputedFields}),
+ _root(new InclusionNode(_policies)) {}
/**
* Creates the data needed to perform an AddFields.
@@ -82,9 +84,7 @@ public:
Document serializeTransformation(
boost::optional<ExplainOptions::Verbosity> explain) const final {
- MutableDocument output;
- _root->serialize(&output, explain);
- return output.freeze();
+ return _root->serialize(explain);
}
/**
@@ -95,14 +95,14 @@ public:
}
DepsTracker::State addDependencies(DepsTracker* deps) const final {
- _root->addDependencies(deps);
+ _root->reportDependencies(deps);
return DepsTracker::State::SEE_NEXT;
}
DocumentSource::GetModPathsReturn getModifiedPaths() const final {
std::set<std::string> computedPaths;
StringMap<std::string> renamedPaths;
- _root->addComputedPaths(&computedPaths, &renamedPaths);
+ _root->reportComputedPaths(&computedPaths, &renamedPaths);
return {DocumentSource::GetModPathsReturn::Type::kFiniteSet,
std::move(computedPaths),
std::move(renamedPaths)};
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
index 950edbec3fc..c8fbfb19f16 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection.cpp
@@ -151,7 +151,8 @@ void ProjectionSpecValidator::parseNestedObject(const BSONObj& thisLevelSpec,
namespace {
-using ProjectionParseMode = ParsedAggregationProjection::ProjectionParseMode;
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
+using ComputedFieldsPolicy = ProjectionPolicies::ComputedFieldsPolicy;
std::string makeBannedComputedFieldsErrorMessage(BSONObj projSpec) {
return str::stream() << "Bad projection specification, cannot use computed fields when parsing "
@@ -169,16 +170,16 @@ public:
* fields (ones which are defined by an expression or a literal) are treated as inclusion
* projections for in this context of the $project stage.
*/
- static TransformerType parse(const BSONObj& spec, ProjectionParseMode parseMode) {
- ProjectTypeParser parser(spec, parseMode);
+ static TransformerType parse(const BSONObj& spec, ProjectionPolicies policies) {
+ ProjectTypeParser parser(spec, policies);
parser.parse();
invariant(parser._parsedType);
return *(parser._parsedType);
}
private:
- ProjectTypeParser(const BSONObj& spec, ProjectionParseMode parseMode)
- : _rawObj(spec), _parseMode(parseMode) {}
+ ProjectTypeParser(const BSONObj& spec, ProjectionPolicies policies)
+ : _rawObj(spec), _policies(policies) {}
/**
* Parses a single BSONElement, with 'fieldName' representing the path used for projection
@@ -223,8 +224,8 @@ private:
* appropriate.
*
* Throws a AssertionException if this element represents a mix of projection types. If we are
- * parsing in ProjectionParseMode::kBanComputedFields mode, an inclusion projection which
- * contains computed fields will also be rejected.
+ * parsing in ComputedFieldsPolicy::kBanComputedFields mode, an inclusion projection
+ * which contains computed fields will also be rejected.
*/
void parseElement(const BSONElement& elem, const FieldPath& pathToElem) {
if (elem.type() == BSONType::Object) {
@@ -236,7 +237,7 @@ private:
uassert(ErrorCodes::FailedToParse,
makeBannedComputedFieldsErrorMessage(_rawObj),
elem.isBoolean() || elem.isNumber() ||
- _parseMode != ProjectionParseMode::kBanComputedFields);
+ _policies.computedFieldsPolicy != ComputedFieldsPolicy::kBanComputedFields);
if (pathToElem.fullPath() == "_id") {
// If the _id field is a computed value, then this must be an inclusion projection. If
@@ -280,8 +281,8 @@ private:
* Traverses 'thisLevelSpec', parsing each element in turn.
*
* Throws a AssertionException if 'thisLevelSpec' represents an invalid mix of projections. If
- * we are parsing in ProjectionParseMode::kBanComputedFields mode, an inclusion projection which
- * contains computed fields will also be rejected.
+ * we are parsing in ComputedFieldsPolicy::kBanComputedFields mode, an inclusion
+ * projection which contains computed fields will also be rejected.
*/
void parseNestedObject(const BSONObj& thisLevelSpec, const FieldPath& prefix) {
@@ -293,7 +294,7 @@ private:
// specified, validate that computed projections are legal, and skip it.
uassert(ErrorCodes::FailedToParse,
makeBannedComputedFieldsErrorMessage(_rawObj),
- _parseMode != ProjectionParseMode::kBanComputedFields);
+ _policies.computedFieldsPolicy != ComputedFieldsPolicy::kBanComputedFields);
uassert(40182,
str::stream() << "Bad projection specification, cannot include fields or "
"add computed fields during an exclusion projection: "
@@ -314,8 +315,8 @@ private:
// This will be populated during parse().
boost::optional<TransformerType> _parsedType;
- // Determines whether an inclusion projection is permitted to contain computed fields.
- ProjectionParseMode _parseMode;
+ // Policies associated with the projection which determine its runtime behaviour.
+ ProjectionPolicies _policies;
};
} // namespace
@@ -323,16 +324,14 @@ private:
std::unique_ptr<ParsedAggregationProjection> ParsedAggregationProjection::create(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const BSONObj& spec,
- ProjectionDefaultIdPolicy defaultIdPolicy,
- ProjectionArrayRecursionPolicy arrayRecursionPolicy,
- ProjectionParseMode parseMode) {
+ ProjectionPolicies policies) {
// Check that the specification was valid. Status returned is unspecific because validate()
// is used by the $addFields stage as well as $project.
// If there was an error, uassert with a $project-specific message.
ProjectionSpecValidator::uassertValid(spec, "$project");
// Check for any conflicting specifications, and determine the type of the projection.
- auto projectionType = ProjectTypeParser::parse(spec, parseMode);
+ auto projectionType = ProjectTypeParser::parse(spec, policies);
// kComputed is a projection type reserved for $addFields, and should never be detected by the
// ProjectTypeParser.
invariant(projectionType != TransformerType::kComputedProjection);
@@ -341,9 +340,9 @@ std::unique_ptr<ParsedAggregationProjection> ParsedAggregationProjection::create
std::unique_ptr<ParsedAggregationProjection> parsedProject(
projectionType == TransformerType::kInclusionProjection
? static_cast<ParsedAggregationProjection*>(
- new ParsedInclusionProjection(expCtx, defaultIdPolicy, arrayRecursionPolicy))
+ new ParsedInclusionProjection(expCtx, policies))
: static_cast<ParsedAggregationProjection*>(
- new ParsedExclusionProjection(expCtx, defaultIdPolicy, arrayRecursionPolicy)));
+ new ParsedExclusionProjection(expCtx, policies)));
// Actually parse the specification.
parsedProject->parse(spec);
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.h b/src/mongo/db/pipeline/parsed_aggregation_projection.h
index af0a028925f..23fd00e1f3f 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.h
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection.h
@@ -142,25 +142,38 @@ private:
*/
class ParsedAggregationProjection : public TransformerInterface {
public:
- // Allows the caller to indicate whether the projection should default to including or excluding
- // the _id field in the event that the projection spec does not specify the desired behavior.
- // For instance, given a projection {a: 1}, specifying 'kExcludeId' is equivalent to projecting
- // {a: 1, _id: 0} while 'kIncludeId' is equivalent to the projection {a: 1, _id: 1}. If the user
- // explicitly specifies a projection on _id, then this will override the default policy; for
- // instance, {a: 1, _id: 0} will exclude _id for both 'kExcludeId' and 'kIncludeId'.
- enum class ProjectionDefaultIdPolicy { kIncludeId, kExcludeId };
-
- // Allows the caller to specify how the projection should handle nested arrays; that is, an
- // array whose immediate parent is itself an array. For example, in the case of sample document
- // {a: [1, 2, [3, 4], {b: [5, 6]}]} the array [3, 4] is a nested array. The array [5, 6] is not,
- // because there is an intervening object between it and its closest array ancestor.
- enum class ProjectionArrayRecursionPolicy { kRecurseNestedArrays, kDoNotRecurseNestedArrays };
-
- // Allows the caller to specify whether computed fields should be allowed within inclusion
- // projections; they are implicitly prohibited within exclusion projections.
- enum class ProjectionParseMode {
- kBanComputedFields, // No computed fields are permitted in the projection spec.
- kAllowComputedFields // Computed fields are permitted.
+ struct ProjectionPolicies {
+ // Allows the caller to indicate whether the projection should default to including or
+ // excluding the _id field in the event that the projection spec does not specify the
+ // desired behavior. For instance, given a projection {a: 1}, specifying 'kExcludeId' is
+ // equivalent to projecting {a: 1, _id: 0} while 'kIncludeId' is equivalent to the
+ // projection {a: 1, _id: 1}. If the user explicitly specifies a projection on _id, then
+ // this will override the default policy; for instance, {a: 1, _id: 0} will exclude _id for
+ // both 'kExcludeId' and 'kIncludeId'.
+ enum class DefaultIdPolicy { kIncludeId, kExcludeId };
+
+ // Allows the caller to specify how the projection should handle nested arrays; that is, an
+ // array whose immediate parent is itself an array. For example, in the case of sample
+ // document {a: [1, 2, [3, 4], {b: [5, 6]}]} the array [3, 4] is a nested array. The array
+ // [5, 6] is not, because there is an intervening object between it and its closest array
+ // ancestor.
+ enum class ArrayRecursionPolicy { kRecurseNestedArrays, kDoNotRecurseNestedArrays };
+
+ // Allows the caller to specify whether computed fields should be allowed within inclusion
+ // projections. Computed fields are implicitly prohibited by exclusion projections.
+ enum class ComputedFieldsPolicy { kBanComputedFields, kAllowComputedFields };
+
+ ProjectionPolicies(
+ DefaultIdPolicy idPolicy = DefaultIdPolicy::kIncludeId,
+ ArrayRecursionPolicy arrayRecursionPolicy = ArrayRecursionPolicy::kRecurseNestedArrays,
+ ComputedFieldsPolicy computedFieldsPolicy = ComputedFieldsPolicy::kAllowComputedFields)
+ : idPolicy(idPolicy),
+ arrayRecursionPolicy(arrayRecursionPolicy),
+ computedFieldsPolicy(computedFieldsPolicy) {}
+
+ DefaultIdPolicy idPolicy;
+ ArrayRecursionPolicy arrayRecursionPolicy;
+ ComputedFieldsPolicy computedFieldsPolicy;
};
/**
@@ -171,9 +184,7 @@ public:
static std::unique_ptr<ParsedAggregationProjection> create(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const BSONObj& spec,
- ProjectionDefaultIdPolicy defaultIdPolicy,
- ProjectionArrayRecursionPolicy arrayRecursionPolicy,
- ProjectionParseMode parseRules = ProjectionParseMode::kAllowComputedFields);
+ ProjectionPolicies policies);
virtual ~ParsedAggregationProjection() = default;
@@ -206,11 +217,8 @@ public:
protected:
ParsedAggregationProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionDefaultIdPolicy defaultIdPolicy,
- ProjectionArrayRecursionPolicy arrayRecursionPolicy)
- : _expCtx(expCtx),
- _arrayRecursionPolicy(arrayRecursionPolicy),
- _defaultIdPolicy(defaultIdPolicy){};
+ ProjectionPolicies policies)
+ : _expCtx(expCtx), _policies(policies){};
/**
* Apply the projection to 'input'.
@@ -219,8 +227,7 @@ protected:
boost::intrusive_ptr<ExpressionContext> _expCtx;
- ProjectionArrayRecursionPolicy _arrayRecursionPolicy;
- ProjectionDefaultIdPolicy _defaultIdPolicy;
+ ProjectionPolicies _policies;
};
} // namespace parsed_aggregation_projection
} // namespace mongo
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp
new file mode 100644
index 00000000000..65f9a36be3d
--- /dev/null
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp
@@ -0,0 +1,266 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
+
+namespace mongo {
+namespace parsed_aggregation_projection {
+
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
+using ArrayRecursionPolicy = ProjectionPolicies::ArrayRecursionPolicy;
+using ComputedFieldsPolicy = ProjectionPolicies::ComputedFieldsPolicy;
+using DefaultIdPolicy = ProjectionPolicies::DefaultIdPolicy;
+
+ProjectionNode::ProjectionNode(ProjectionPolicies policies, std::string pathToNode)
+ : _policies(policies), _pathToNode(std::move(pathToNode)) {}
+
+void ProjectionNode::addProjectionForPath(const FieldPath& path) {
+ if (path.getPathLength() == 1) {
+ _projectedFields.insert(path.fullPath());
+ return;
+ }
+ // FieldPath can't be empty, so it is safe to obtain the first path component here.
+ addOrGetChild(path.getFieldName(0).toString())->addProjectionForPath(path.tail());
+}
+
+void ProjectionNode::addExpressionForPath(const FieldPath& path,
+ boost::intrusive_ptr<Expression> expr) {
+ // If the computed fields policy is 'kBanComputedFields', we should never reach here.
+ invariant(_policies.computedFieldsPolicy == ComputedFieldsPolicy::kAllowComputedFields);
+ if (path.getPathLength() == 1) {
+ auto fieldName = path.fullPath();
+ _expressions[fieldName] = expr;
+ _orderToProcessAdditionsAndChildren.push_back(fieldName);
+ return;
+ }
+ // FieldPath can't be empty, so it is safe to obtain the first path component here.
+ addOrGetChild(path.getFieldName(0).toString())->addExpressionForPath(path.tail(), expr);
+}
+
+ProjectionNode* ProjectionNode::addOrGetChild(const std::string& field) {
+ auto child = getChild(field);
+ return child ? child : addChild(field);
+}
+
+ProjectionNode* ProjectionNode::addChild(const std::string& field) {
+ invariant(!str::contains(field, "."));
+ _orderToProcessAdditionsAndChildren.push_back(field);
+ auto insertedPair = _children.emplace(std::make_pair(field, makeChild(field)));
+ return insertedPair.first->second.get();
+}
+
+ProjectionNode* ProjectionNode::getChild(const std::string& field) const {
+ auto childIt = _children.find(field);
+ return childIt == _children.end() ? nullptr : childIt->second.get();
+}
+
+Document ProjectionNode::applyToDocument(const Document& inputDoc) const {
+ // Defer to the derived class to initialize the output document, then apply.
+ MutableDocument outputDoc{initializeOutputDocument(inputDoc)};
+ applyProjections(inputDoc, &outputDoc);
+ applyExpressions(inputDoc, &outputDoc);
+
+ // Make sure that we always pass through any metadata present in the input doc.
+ outputDoc.copyMetaDataFrom(inputDoc);
+ return outputDoc.freeze();
+}
+
+void ProjectionNode::applyProjections(const Document& inputDoc, MutableDocument* outputDoc) const {
+ // Iterate over the input document so that the projected document retains its field ordering.
+ auto it = inputDoc.fieldIterator();
+ while (it.more()) {
+ auto fieldPair = it.next();
+ auto fieldName = fieldPair.first.toString();
+ if (_projectedFields.count(fieldName)) {
+ outputDoc->setField(fieldName, applyLeafProjectionToValue(fieldPair.second));
+ continue;
+ }
+
+ auto childIt = _children.find(fieldName);
+ if (childIt != _children.end()) {
+ outputDoc->setField(fieldName,
+ childIt->second->applyProjectionsToValue(fieldPair.second));
+ }
+ }
+
+ // Ensure we project all specified fields, including those not present in the input document.
+ const bool shouldProjectNonExistentFields = applyLeafProjectionToValue(Value(true)).missing();
+ for (auto&& fieldName : _projectedFields) {
+ if (shouldProjectNonExistentFields && inputDoc[fieldName].missing()) {
+ outputDoc->setField(fieldName, applyLeafProjectionToValue(inputDoc[fieldName]));
+ }
+ }
+}
+
+Value ProjectionNode::applyProjectionsToValue(Value inputValue) const {
+ if (inputValue.getType() == BSONType::Object) {
+ MutableDocument outputSubDoc{initializeOutputDocument(inputValue.getDocument())};
+ applyProjections(inputValue.getDocument(), &outputSubDoc);
+ return outputSubDoc.freezeToValue();
+ } else if (inputValue.getType() == BSONType::Array) {
+ std::vector<Value> values = inputValue.getArray();
+ for (auto& value : values) {
+ // If this is a nested array and our policy is to not recurse, skip the array.
+ // Otherwise, descend into the array and project each element individually.
+ const bool shouldSkip = value.isArray() &&
+ _policies.arrayRecursionPolicy == ArrayRecursionPolicy::kDoNotRecurseNestedArrays;
+ value = (shouldSkip ? transformSkippedValueForOutput(value)
+ : applyProjectionsToValue(value));
+ }
+ return Value(std::move(values));
+ } else {
+ // This represents the case where we are projecting children of a field which does not have
+ // any children; for instance, applying the projection {"a.b": true} to the document {a: 2}.
+ return transformSkippedValueForOutput(inputValue);
+ }
+}
+
+void ProjectionNode::applyExpressions(const Document& root, MutableDocument* outputDoc) const {
+ for (auto&& field : _orderToProcessAdditionsAndChildren) {
+ auto childIt = _children.find(field);
+ if (childIt != _children.end()) {
+ outputDoc->setField(
+ field, childIt->second->applyExpressionsToValue(root, outputDoc->peek()[field]));
+ } else {
+ auto expressionIt = _expressions.find(field);
+ invariant(expressionIt != _expressions.end());
+ outputDoc->setField(field, expressionIt->second->evaluate(root));
+ }
+ }
+}
+
+Value ProjectionNode::applyExpressionsToValue(const Document& root, Value inputValue) const {
+ if (inputValue.getType() == BSONType::Object) {
+ MutableDocument outputDoc(inputValue.getDocument());
+ applyExpressions(root, &outputDoc);
+ return outputDoc.freezeToValue();
+ } else if (inputValue.getType() == BSONType::Array) {
+ std::vector<Value> values = inputValue.getArray();
+ for (auto& value : values) {
+ value = applyExpressionsToValue(root, value);
+ }
+ return Value(std::move(values));
+ } else {
+ if (subtreeContainsComputedFields()) {
+ // Our semantics in this case are to replace whatever existing value we find with a new
+ // document of all the computed values. This case represents applying a projection like
+ // {"a.b": {$literal: 1}} to the document {a: 1}. This should yield {a: {b: 1}}.
+ MutableDocument outputDoc;
+ applyExpressions(root, &outputDoc);
+ return outputDoc.freezeToValue();
+ }
+ // We didn't have any expressions, so just skip this value.
+ return transformSkippedValueForOutput(inputValue);
+ }
+}
+
+bool ProjectionNode::subtreeContainsComputedFields() const {
+ return (!_expressions.empty()) ||
+ std::any_of(_children.begin(), _children.end(), [](const auto& childPair) {
+ return childPair.second->subtreeContainsComputedFields();
+ });
+}
+
+void ProjectionNode::reportProjectedPaths(std::set<std::string>* projectedPaths) const {
+ for (auto&& projectedField : _projectedFields) {
+ projectedPaths->insert(FieldPath::getFullyQualifiedPath(_pathToNode, projectedField));
+ }
+
+ for (auto&& childPair : _children) {
+ childPair.second->reportProjectedPaths(projectedPaths);
+ }
+}
+
+void ProjectionNode::reportComputedPaths(std::set<std::string>* computedPaths,
+ StringMap<std::string>* renamedPaths) const {
+ for (auto&& computedPair : _expressions) {
+ // The expression's path is the concatenation of the path to this node, plus the field name
+ // associated with the expression.
+ auto exprPath = FieldPath::getFullyQualifiedPath(_pathToNode, computedPair.first);
+ auto exprComputedPaths = computedPair.second->getComputedPaths(exprPath);
+ computedPaths->insert(exprComputedPaths.paths.begin(), exprComputedPaths.paths.end());
+
+ for (auto&& rename : exprComputedPaths.renames) {
+ (*renamedPaths)[rename.first] = rename.second;
+ }
+ }
+ for (auto&& childPair : _children) {
+ childPair.second->reportComputedPaths(computedPaths, renamedPaths);
+ }
+}
+
+void ProjectionNode::optimize() {
+ for (auto&& expressionIt : _expressions) {
+ _expressions[expressionIt.first] = expressionIt.second->optimize();
+ }
+ for (auto&& childPair : _children) {
+ childPair.second->optimize();
+ }
+}
+
+Document ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
+ MutableDocument outputDoc;
+ serialize(explain, &outputDoc);
+ return outputDoc.freeze();
+}
+
+void ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explain,
+ MutableDocument* output) const {
+ // Determine the boolean value for projected fields in the explain output.
+ const bool projVal = !applyLeafProjectionToValue(Value(true)).missing();
+
+ // Always put "_id" first if it was projected (implicitly or explicitly).
+ if (_projectedFields.find("_id") != _projectedFields.end()) {
+ output->addField("_id", Value(projVal));
+ }
+
+ for (auto&& projectedField : _projectedFields) {
+ if (projectedField != "_id") {
+ output->addField(projectedField, Value(projVal));
+ }
+ }
+
+ for (auto&& field : _orderToProcessAdditionsAndChildren) {
+ auto childIt = _children.find(field);
+ if (childIt != _children.end()) {
+ MutableDocument subDoc;
+ childIt->second->serialize(explain, &subDoc);
+ output->addField(field, subDoc.freezeToValue());
+ } else {
+ invariant(_policies.computedFieldsPolicy == ComputedFieldsPolicy::kAllowComputedFields);
+ auto expressionIt = _expressions.find(field);
+ invariant(expressionIt != _expressions.end());
+ output->addField(field, expressionIt->second->serialize(static_cast<bool>(explain)));
+ }
+ }
+}
+
+} // namespace parsed_aggregation_projection
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_node.h b/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
new file mode 100644
index 00000000000..2f7e499d621
--- /dev/null
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
@@ -0,0 +1,190 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/pipeline/parsed_aggregation_projection.h"
+
+namespace mongo {
+namespace parsed_aggregation_projection {
+
+/**
+ * A node used to define the parsed structure of a projection. Each ProjectionNode represents one
+ * 'level' of the parsed specification. The root ProjectionNode represents all top level projections
+ * or additions, with any child ProjectionNodes representing dotted or nested projections or
+ * additions.
+ *
+ * ProjectionNode is an abstract base class which implements all the generic construction, traversal
+ * and execution functionality common to different projection types. Each derived class need only
+ * provide a minimal set of virtual function implementations dictating, for instance, how the
+ * projection should behave upon reaching a leaf node.
+ */
+class ProjectionNode {
+public:
+ using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
+
+ ProjectionNode(ProjectionPolicies policies, std::string pathToNode = "");
+
+ virtual ~ProjectionNode() = default;
+
+ /**
+ * Recursively adds 'path' into the tree as a projected field, creating any child nodes if
+ * necessary.
+ *
+ * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in
+ * the tree. For example, it is an error to add the path "a.b" from a tree which has already
+ * added a computed field "a".
+ */
+ void addProjectionForPath(const FieldPath& path);
+
+ /**
+ * Recursively adds 'path' into the tree as a computed field, creating any child nodes if
+ * necessary.
+ *
+ * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in
+ * the tree. For example, it is an error to add the path "a.b" as a computed field to a tree
+ * which has already projected the field "a".
+ */
+ void addExpressionForPath(const FieldPath& path, boost::intrusive_ptr<Expression> expr);
+
+ /**
+ * Creates the child if it doesn't already exist. 'field' is not allowed to be dotted. Returns
+ * the child node if it already exists, or the newly-created child otherwise.
+ */
+ ProjectionNode* addOrGetChild(const std::string& field);
+
+ /**
+ * Applies all projections and expressions, if applicable, and returns the resulting document.
+ */
+ Document applyToDocument(const Document& inputDoc) const;
+
+ /**
+ * Recursively evaluates all expressions in the projection, writing the results to 'outputDoc'.
+ */
+ void applyExpressions(const Document& root, MutableDocument* outputDoc) const;
+
+ /**
+ * Reports dependencies on any fields that are required by this projection.
+ */
+ virtual void reportDependencies(DepsTracker* deps) const = 0;
+
+ /**
+ * Recursively report all paths that are referenced by this projection.
+ */
+ void reportProjectedPaths(std::set<std::string>* preservedPaths) const;
+
+ /**
+ * Recursively reports all computed paths in this projection, adding them into 'computedPaths'.
+ *
+ * Computed paths that are identified as the result of a simple rename are instead filled out in
+ * 'renamedPaths'. Each entry in 'renamedPaths' maps from the path's new name to its old name
+ * prior to application of this projection.
+ */
+ void reportComputedPaths(std::set<std::string>* computedPaths,
+ StringMap<std::string>* renamedPaths) const;
+
+ const std::string& getPath() const {
+ return _pathToNode;
+ }
+
+ void optimize();
+
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const;
+
+ void serialize(boost::optional<ExplainOptions::Verbosity> explain,
+ MutableDocument* output) const;
+
+protected:
+ // Returns a unique_ptr to a new instance of the implementing class for the given 'fieldName'.
+ virtual std::unique_ptr<ProjectionNode> makeChild(std::string fieldName) const = 0;
+
+ // Returns the initial document to which the current level of the projection should be applied.
+ // For an inclusion projection this will be an empty document, to which we will add the fields
+ // we wish to retain. For an exclusion this will be the complete document, from which we will
+ // eliminate the fields we wish to omit.
+ virtual Document initializeOutputDocument(const Document& inputDoc) const = 0;
+
+ // Given an input leaf value, returns the value that should be added to the output document.
+ // Depending on the projection type this will be either the value itself, or "missing".
+ virtual Value applyLeafProjectionToValue(const Value& value) const = 0;
+
+ // Given an input leaf that we intend to skip over, returns the value that should be added to
+ // the output document. Depending on the projection this will be either the value, or "missing".
+ virtual Value transformSkippedValueForOutput(const Value&) const = 0;
+
+ // TODO use StringMap once SERVER-23700 is resolved.
+ stdx::unordered_map<std::string, std::unique_ptr<ProjectionNode>> _children;
+ stdx::unordered_map<size_t, std::unique_ptr<ProjectionNode>> _arrayBranches;
+
+ StringMap<boost::intrusive_ptr<Expression>> _expressions;
+ stdx::unordered_set<std::string> _projectedFields;
+
+ ProjectionPolicies _policies;
+
+ std::string _pathToNode;
+
+private:
+ // Iterates 'inputDoc' for each projected field, adding to or removing from 'outputDoc'. Also
+ // copies over enough information to preserve the structure of the incoming document for the
+ // fields this projection cares about.
+ //
+ // For example, given a ProjectionNode tree representing this projection:
+ // {a: {b: 1, c: <exp>}, "d.e": <exp>}
+ // Calling applyProjections() with an 'inputDoc' of
+ // {a: [{b: 1, d: 1}, {b: 2, d: 2}], d: [{e: 1, f: 1}, {e: 1, f: 1}]}
+ // and an empty 'outputDoc' will leave 'outputDoc' representing the document
+ // {a: [{b: 1}, {b: 2}], d: [{}, {}]}
+ void applyProjections(const Document& inputDoc, MutableDocument* outputDoc) const;
+
+ // Helpers for the 'applyProjections' and 'applyExpressions' methods. Applies the transformation
+ // recursively to each element of any arrays, and ensures primitives are handled appropriately.
+ Value applyExpressionsToValue(const Document& root, Value inputVal) const;
+ Value applyProjectionsToValue(Value inputVal) const;
+
+ // Adds a new ProjectionNode as a child. 'field' cannot be dotted.
+ ProjectionNode* addChild(const std::string& field);
+
+ // Returns nullptr if no such child exists.
+ ProjectionNode* getChild(const std::string& field) const;
+
+ // Returns true if this node or any child of this node contains a computed field.
+ bool subtreeContainsComputedFields() const;
+
+ // Our projection semantics are such that all field additions need to be processed in the order
+ // specified. '_orderToProcessAdditionsAndChildren' tracks that order.
+ //
+ // For example, for the specification {a: <expression>, "b.c": <expression>, d: <expression>},
+ // we need to add the top level fields in the order "a", then "b", then "d". This ordering
+ // information needs to be tracked separately, since "a" and "d" will be tracked via
+ // '_expressions', and "b.c" will be tracked as a child ProjectionNode in '_children'. For the
+ // example above, '_orderToProcessAdditionsAndChildren' would be ["a", "b", "d"].
+ std::vector<std::string> _orderToProcessAdditionsAndChildren;
+};
+
+} // namespace parsed_aggregation_projection
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
index 9d224b7adbe..5a2d933246d 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
@@ -46,24 +46,27 @@ namespace mongo {
namespace parsed_aggregation_projection {
namespace {
-using ProjectionArrayRecursionPolicy = ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
-using ProjectionDefaultIdPolicy = ParsedAggregationProjection::ProjectionDefaultIdPolicy;
-using ProjectionParseMode = ParsedAggregationProjection::ProjectionParseMode;
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
template <typename T>
BSONObj wrapInLiteral(const T& arg) {
return BSON("$literal" << arg);
}
-// Helper to simplify the creation of a ParsedAggregationProjection which includes _id and recurses
-// nested arrays by default.
-std::unique_ptr<ParsedAggregationProjection> makeProjectionWithDefaultPolicies(BSONObj projSpec) {
+// Helper to simplify the creation of a ParsedAggregationProjection with default policies.
+std::unique_ptr<ParsedAggregationProjection> makeProjectionWithDefaultPolicies(BSONObj spec) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- return ParsedAggregationProjection::create(
- expCtx,
- projSpec,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ ParsedAggregationProjection::ProjectionPolicies defaultPolicies;
+ return ParsedAggregationProjection::create(expCtx, spec, defaultPolicies);
+}
+
+// Helper to simplify the creation of a ParsedAggregationProjection which bans computed fields.
+std::unique_ptr<ParsedAggregationProjection> makeProjectionWithBannedComputedFields(BSONObj spec) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ParsedAggregationProjection::ProjectionPolicies banComputedFields;
+ banComputedFields.computedFieldsPolicy =
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields;
+ return ParsedAggregationProjection::create(expCtx, spec, banComputedFields);
}
//
@@ -71,7 +74,6 @@ std::unique_ptr<ParsedAggregationProjection> makeProjectionWithDefaultPolicies(B
//
TEST(ParsedAggregationProjectionErrors, ShouldRejectDuplicateFieldNames) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude the same field twice.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "a" << true)),
AssertionException);
@@ -94,7 +96,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectDuplicateFieldNames) {
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectDuplicateIds) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude _id twice.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id" << true << "_id" << true)),
AssertionException);
@@ -116,7 +117,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectDuplicateIds) {
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectFieldsWithSharedPrefix) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude Fields with a shared prefix.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "a.b" << true)),
AssertionException);
@@ -140,7 +140,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectFieldsWithSharedPrefix) {
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectPathConflictsWithNonAlphaNumericCharacters) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude non-alphanumeric fields with a shared prefix. First assert that the non-
// alphanumeric fields are accepted when no prefixes are present.
ASSERT(makeProjectionWithDefaultPolicies(
@@ -202,7 +201,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectPathConflictsWithNonAlphaNum
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfIdAndSubFieldsOfId) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Include/exclude _id twice.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id" << true << "_id.x" << true)),
AssertionException);
@@ -227,8 +225,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfIdAndSubFieldsOfId) {
}
TEST(ParsedAggregationProjectionErrors, ShouldAllowMixOfIdInclusionAndExclusion) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
-
// Mixing "_id" inclusion with exclusion.
auto parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << true << "a" << false));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
@@ -241,7 +237,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldAllowMixOfIdInclusionAndExclusion)
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfInclusionAndExclusion) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Simple mix.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "b" << false)),
AssertionException);
@@ -269,7 +264,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfInclusionAndExclusion)
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfExclusionAndComputedFields) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "b" << wrapInLiteral(1))),
AssertionException);
@@ -294,7 +288,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectMixOfExclusionAndComputedFie
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectDottedFieldInSubDocument) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("b.c" << true))),
AssertionException);
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("b.c" << wrapInLiteral(1)))),
@@ -302,7 +295,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectDottedFieldInSubDocument) {
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectFieldNamesStartingWithADollar) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$dollar" << 0)), AssertionException);
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$dollar" << 1)), AssertionException);
@@ -319,13 +311,11 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectFieldNamesStartingWithADolla
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectTopLevelExpressions) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$add" << BSON_ARRAY(4 << 2))),
AssertionException);
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectExpressionWithMultipleFieldNames) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(
BSON("a" << BSON("$add" << BSON_ARRAY(4 << 2) << "b" << 1))),
AssertionException);
@@ -345,7 +335,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectEmptyProjection) {
}
TEST(ParsedAggregationProjectionErrors, ShouldRejectEmptyNestedObject) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSONObj())), AssertionException);
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "b" << BSONObj())),
AssertionException);
@@ -357,7 +346,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldRejectEmptyNestedObject) {
}
TEST(ParsedAggregationProjectionErrors, ShouldErrorOnInvalidExpression) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(makeProjectionWithDefaultPolicies(
BSON("a" << false << "b" << BSON("$unknown" << BSON_ARRAY(4 << 2)))),
AssertionException);
@@ -367,7 +355,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldErrorOnInvalidExpression) {
}
TEST(ParsedAggregationProjectionErrors, ShouldErrorOnInvalidFieldPath) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Empty field names.
ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << wrapInLiteral(2))),
AssertionException);
@@ -401,7 +388,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldErrorOnInvalidFieldPath) {
}
TEST(ParsedAggregationProjectionErrors, ShouldNotErrorOnTwoNestedFields) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a.c" << true));
makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a" << BSON("c" << true)));
}
@@ -411,7 +397,6 @@ TEST(ParsedAggregationProjectionErrors, ShouldNotErrorOnTwoNestedFields) {
//
TEST(ParsedAggregationProjectionType, ShouldDefaultToInclusionProjection) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
@@ -423,7 +408,6 @@ TEST(ParsedAggregationProjectionType, ShouldDefaultToInclusionProjection) {
}
TEST(ParsedAggregationProjectionType, ShouldDetectExclusionProjection) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << false));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
@@ -441,7 +425,6 @@ TEST(ParsedAggregationProjectionType, ShouldDetectExclusionProjection) {
}
TEST(ParsedAggregationProjectionType, ShouldDetectInclusionProjection) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
@@ -462,7 +445,6 @@ TEST(ParsedAggregationProjectionType, ShouldDetectInclusionProjection) {
}
TEST(ParsedAggregationProjectionType, ShouldTreatOnlyComputedFieldsAsAnInclusionProjection) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
@@ -485,7 +467,6 @@ TEST(ParsedAggregationProjectionType, ShouldTreatOnlyComputedFieldsAsAnInclusion
}
TEST(ParsedAggregationProjectionType, ShouldAllowMixOfInclusionAndComputedFields) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto parsedProject =
makeProjectionWithDefaultPolicies(BSON("a" << true << "b" << wrapInLiteral(1)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
@@ -504,172 +485,79 @@ TEST(ParsedAggregationProjectionType, ShouldAllowMixOfInclusionAndComputedFields
}
TEST(ParsedAggregationProjectionType, ShouldRejectMixOfInclusionAndBannedComputedFields) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
ASSERT_THROWS(
- ParsedAggregationProjection::create(expCtx,
- BSON("a" << true << "b" << wrapInLiteral(1)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ makeProjectionWithBannedComputedFields(BSON("a" << true << "b" << wrapInLiteral(1))),
AssertionException);
ASSERT_THROWS(
- ParsedAggregationProjection::create(expCtx,
- BSON("a.b" << true << "a.c" << wrapInLiteral(1)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ makeProjectionWithBannedComputedFields(BSON("a.b" << true << "a.c" << wrapInLiteral(1))),
AssertionException);
- ASSERT_THROWS(ParsedAggregationProjection::create(
- expCtx,
- BSON("a" << BSON("b" << true << "c" << wrapInLiteral(1))),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << true << "c" << wrapInLiteral(1)))),
AssertionException);
- ASSERT_THROWS(
- ParsedAggregationProjection::create(expCtx,
- BSON("a" << BSON("b" << true << "c"
- << "stringLiteral")),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
- AssertionException);
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(BSON("a" << BSON("b" << true << "c"
+ << "stringLiteral"))),
+ AssertionException);
}
TEST(ParsedAggregationProjectionType, ShouldRejectOnlyComputedFieldsWhenComputedFieldsAreBanned) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ASSERT_THROWS(ParsedAggregationProjection::create(
- expCtx,
- BSON("a" << wrapInLiteral(1) << "b" << wrapInLiteral(2)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << wrapInLiteral(1) << "b" << wrapInLiteral(2))),
AssertionException);
- ASSERT_THROWS(ParsedAggregationProjection::create(
- expCtx,
- BSON("a.b" << wrapInLiteral(1) << "a.c" << wrapInLiteral(2)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a.b" << wrapInLiteral(1) << "a.c" << wrapInLiteral(2))),
AssertionException);
- ASSERT_THROWS(ParsedAggregationProjection::create(
- expCtx,
- BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2))),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2)))),
AssertionException);
- ASSERT_THROWS(ParsedAggregationProjection::create(
- expCtx,
- BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2))),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields),
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2)))),
AssertionException);
}
TEST(ParsedAggregationProjectionType, ShouldAcceptInclusionProjectionWhenComputedFieldsAreBanned) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("a" << true),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ auto parsedProject = makeProjectionWithBannedComputedFields(BSON("a" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id" << false << "a" << true),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false << "a" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id" << false << "a.b.c" << true),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false << "a.b.c" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id.x" << true),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id.x" << true));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id" << BSON("x" << true)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << BSON("x" << true)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("x" << BSON("_id" << true)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("x" << BSON("_id" << true)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
}
TEST(ParsedAggregationProjectionType, ShouldAcceptExclusionProjectionWhenComputedFieldsAreBanned) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("a" << false),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ auto parsedProject = makeProjectionWithBannedComputedFields(BSON("a" << false));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id.x" << false),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id.x" << false));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id" << BSON("x" << false)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << BSON("x" << false)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("x" << BSON("_id" << false)),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("x" << BSON("_id" << false)));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
- parsedProject =
- ParsedAggregationProjection::create(expCtx,
- BSON("_id" << false),
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionParseMode::kBanComputedFields);
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false));
ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
}
TEST(ParsedAggregationProjectionType, ShouldCoerceNumericsToBools) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
std::vector<Value> zeros = {Value(0), Value(0LL), Value(0.0), Value(Decimal128(0))};
for (auto&& zero : zeros) {
auto parsedProject = makeProjectionWithDefaultPolicies(Document{{"a", zero}}.toBson());
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
index a4e42f9561e..510abf26419 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection.cpp
@@ -41,115 +41,13 @@ namespace mongo {
namespace parsed_aggregation_projection {
-//
-// ExclusionNode.
-//
-
-ExclusionNode::ExclusionNode(ProjectionArrayRecursionPolicy recursionPolicy, std::string pathToNode)
- : _arrayRecursionPolicy(recursionPolicy), _pathToNode(std::move(pathToNode)) {}
-
-Document ExclusionNode::serialize() const {
- MutableDocument output;
- for (auto&& excludedField : _excludedFields) {
- output.addField(excludedField, Value(false));
- }
-
- for (auto&& childPair : _children) {
- output.addField(childPair.first, Value(childPair.second->serialize()));
- }
- return output.freeze();
-}
-
-void ExclusionNode::excludePath(FieldPath path) {
- if (path.getPathLength() == 1) {
- _excludedFields.insert(path.fullPath());
- return;
- }
- addOrGetChild(path.getFieldName(0))->excludePath(path.tail());
-}
-
-Document ExclusionNode::applyProjection(const Document& input) const {
- MutableDocument output(input);
- for (auto&& field : _excludedFields) {
- output.remove(field);
- }
- for (auto&& childPair : _children) {
- output[childPair.first] = childPair.second->applyProjectionToValue(input[childPair.first]);
- }
- return output.freeze();
-}
-
-ExclusionNode* ExclusionNode::addOrGetChild(FieldPath fieldPath) {
- invariant(fieldPath.getPathLength() == 1);
- auto child = getChild(fieldPath.fullPath());
- return child ? child : addChild(fieldPath.fullPath());
-}
-
-ExclusionNode* ExclusionNode::getChild(std::string field) const {
- auto it = _children.find(field);
- return it == _children.end() ? nullptr : it->second.get();
-}
-
-ExclusionNode* ExclusionNode::addChild(std::string field) {
- auto pathToChild = _pathToNode.empty() ? field : _pathToNode + "." + field;
-
- auto emplacedPair = _children.emplace(std::make_pair(
- std::move(field), stdx::make_unique<ExclusionNode>(_arrayRecursionPolicy, pathToChild)));
-
- // emplacedPair is a pair<iterator position, bool inserted>.
- invariant(emplacedPair.second);
-
- return emplacedPair.first->second.get();
-}
-
-Value ExclusionNode::applyProjectionToValue(Value val) const {
- switch (val.getType()) {
- case BSONType::Object:
- return Value(applyProjection(val.getDocument()));
- case BSONType::Array: {
- // Apply exclusion to each element of the array. Note that numeric paths aren't treated
- // specially, and we will always apply the projection to each element in the array.
- //
- // For example, applying the projection {"a.1": 0} to the document
- // {a: [{b: 0, "1": 0}, {b: 1, "1": 1}]} will not result in {a: [{b: 0, "1": 0}]}, but
- // instead will result in {a: [{b: 0}, {b: 1}]}.
- std::vector<Value> values = val.getArray();
- for (auto it = values.begin(); it != values.end(); it++) {
- // If this is a nested array and our policy is to not recurse, leave the array
- // as-is. Otherwise, descend into the array and project each element individually.
- const bool shouldSkip = it->isArray() &&
- _arrayRecursionPolicy ==
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays;
- *it = (shouldSkip ? *it : applyProjectionToValue(*it));
- }
- return Value(std::move(values));
- }
- default:
- return val;
- }
-}
-
-void ExclusionNode::addModifiedPaths(std::set<std::string>* modifiedPaths) const {
- for (auto&& excludedField : _excludedFields) {
- modifiedPaths->insert(FieldPath::getFullyQualifiedPath(_pathToNode, excludedField));
- }
-
- for (auto&& childPair : _children) {
- childPair.second->addModifiedPaths(modifiedPaths);
- }
-}
-
-//
-// ParsedExclusionProjection.
-//
-
Document ParsedExclusionProjection::serializeTransformation(
boost::optional<ExplainOptions::Verbosity> explain) const {
- return _root->serialize();
+ return _root->serialize(explain);
}
Document ParsedExclusionProjection::applyProjection(const Document& inputDoc) const {
- return _root->applyProjection(inputDoc);
+ return _root->applyToDocument(inputDoc);
}
void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node, size_t depth) {
@@ -174,7 +72,7 @@ void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node,
// which is permitted to be explicitly included here.
invariant(!elem.trueValue() || elem.fieldNameStringData() == "_id"_sd);
if (!elem.trueValue()) {
- node->excludePath(FieldPath(fieldName));
+ node->addProjectionForPath(FieldPath(fieldName));
}
break;
}
@@ -184,7 +82,7 @@ void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node,
ExclusionNode* child;
if (elem.fieldNameStringData().find('.') == std::string::npos) {
- child = node->addOrGetChild(fieldName);
+ child = node->addOrGetChild(fieldName.toString());
} else {
// A dotted field is not allowed in a sub-object, and should have been detected
// in ParsedAggregationProjection's parsing before we get here.
@@ -195,7 +93,7 @@ void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node,
child = node;
auto fullPath = FieldPath(fieldName);
while (fullPath.getPathLength() > 1) {
- child = child->addOrGetChild(fullPath.getFieldName(0));
+ child = child->addOrGetChild(fullPath.getFieldName(0).toString());
fullPath = fullPath.tail();
}
// It is illegal to construct an empty FieldPath, so the above loop ends one
@@ -212,8 +110,8 @@ void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node,
// If _id was not specified, then doing nothing will cause it to be included. If the default _id
// policy is kExcludeId, we add a new entry for _id to the ExclusionNode tree here.
- if (!idSpecified && _defaultIdPolicy == ProjectionDefaultIdPolicy::kExcludeId) {
- _root->excludePath({FieldPath("_id")});
+ if (!idSpecified && _policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kExcludeId) {
+ _root->addProjectionForPath({FieldPath("_id")});
}
}
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection.h b/src/mongo/db/pipeline/parsed_exclusion_projection.h
index 52187a65a56..ee6c2aa867f 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection.h
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection.h
@@ -34,6 +34,7 @@
#include <string>
#include "mongo/db/pipeline/parsed_aggregation_projection.h"
+#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
#include "mongo/stdx/unordered_map.h"
#include "mongo/stdx/unordered_set.h"
@@ -49,50 +50,37 @@ namespace parsed_aggregation_projection {
* represents one 'level' of the parsed specification. The root ExclusionNode represents all top
* level exclusions, with any child ExclusionNodes representing dotted or nested exclusions.
*/
-class ExclusionNode {
+class ExclusionNode final : public ProjectionNode {
public:
- using ProjectionArrayRecursionPolicy =
- ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
-
- ExclusionNode(ProjectionArrayRecursionPolicy recursionPolicy, std::string pathToNode = "");
-
- /**
- * Serialize this exclusion.
- */
- Document serialize() const;
-
- /**
- * Mark this path to be excluded. 'path' is allowed to be dotted.
- */
- void excludePath(FieldPath path);
-
- /**
- * Applies this tree of exclusions to the input document.
- */
- Document applyProjection(const Document& input) const;
-
- /**
- * Creates the child if it doesn't already exist. 'field' is not allowed to be dotted.
- */
- ExclusionNode* addOrGetChild(FieldPath field);
-
- void addModifiedPaths(std::set<std::string>* modifiedPaths) const;
-
-private:
- // Helpers for addOrGetChild above.
- ExclusionNode* getChild(std::string field) const;
- ExclusionNode* addChild(std::string field);
-
- // Helper for applyProjection above.
- Value applyProjectionToValue(Value val) const;
+ ExclusionNode(ProjectionPolicies policies, std::string pathToNode = "")
+ : ProjectionNode(policies, std::move(pathToNode)) {
+ // Computed fields are not supported by exclusion projections.
+ invariant(_policies.computedFieldsPolicy ==
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields);
+ }
- // Fields excluded at this level.
- stdx::unordered_set<std::string> _excludedFields;
+ ExclusionNode* addOrGetChild(const std::string& field) {
+ return static_cast<ExclusionNode*>(ProjectionNode::addOrGetChild(field));
+ }
- ProjectionArrayRecursionPolicy _arrayRecursionPolicy;
+ void reportDependencies(DepsTracker* deps) const final {
+ // We have no dependencies on specific fields, since we only know the fields to be removed.
+ }
- std::string _pathToNode;
- stdx::unordered_map<std::string, std::unique_ptr<ExclusionNode>> _children;
+protected:
+ std::unique_ptr<ProjectionNode> makeChild(std::string fieldName) const final {
+ return std::make_unique<ExclusionNode>(
+ _policies, FieldPath::getFullyQualifiedPath(_pathToNode, fieldName));
+ }
+ Document initializeOutputDocument(const Document& inputDoc) const final {
+ return inputDoc;
+ }
+ Value applyLeafProjectionToValue(const Value& value) const final {
+ return Value();
+ }
+ Value transformSkippedValueForOutput(const Value& value) const final {
+ return value;
+ }
};
/**
@@ -105,10 +93,13 @@ private:
class ParsedExclusionProjection : public ParsedAggregationProjection {
public:
ParsedExclusionProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionDefaultIdPolicy defaultIdPolicy,
- ProjectionArrayRecursionPolicy arrayRecursionPolicy)
- : ParsedAggregationProjection(expCtx, defaultIdPolicy, arrayRecursionPolicy),
- _root(new ExclusionNode(_arrayRecursionPolicy)) {}
+ ProjectionPolicies policies)
+ : ParsedAggregationProjection(
+ expCtx,
+ {policies.idPolicy,
+ policies.arrayRecursionPolicy,
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields}),
+ _root(new ExclusionNode(_policies)) {}
TransformerType getType() const final {
return TransformerType::kExclusionProjection;
@@ -135,7 +126,7 @@ public:
DocumentSource::GetModPathsReturn getModifiedPaths() const final {
std::set<std::string> modifiedPaths;
- _root->addModifiedPaths(&modifiedPaths);
+ _root->reportProjectedPaths(&modifiedPaths);
return {DocumentSource::GetModPathsReturn::Type::kFiniteSet, std::move(modifiedPaths), {}};
}
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
index 6b8db5b0990..1b19d5cb664 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
@@ -51,25 +51,39 @@ namespace mongo {
namespace parsed_aggregation_projection {
namespace {
-using ProjectionArrayRecursionPolicy = ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
-using ProjectionDefaultIdPolicy = ParsedAggregationProjection::ProjectionDefaultIdPolicy;
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
using std::vector;
-// Helper to simplify the creation of a ParsedExclusionProjection which includes _id and recurses
-// nested arrays by default.
+// Helper to simplify the creation of a ParsedExclusionProjection with default policies.
ParsedExclusionProjection makeExclusionProjectionWithDefaultPolicies() {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- return ParsedExclusionProjection(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ ParsedAggregationProjection::ProjectionPolicies defaultPolicies;
+ return {expCtx, defaultPolicies};
+}
+
+// Helper to simplify the creation of a ParsedExclusionProjection which excludes _id by default.
+ParsedExclusionProjection makeExclusionProjectionWithDefaultIdExclusion() {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ParsedAggregationProjection::ProjectionPolicies defaultExcludeId;
+ defaultExcludeId.idPolicy = ProjectionPolicies::DefaultIdPolicy::kExcludeId;
+ return {expCtx, defaultExcludeId};
+}
+
+// Helper to simplify the creation of a ParsedExclusionProjection which does not recurse arrays.
+ParsedExclusionProjection makeExclusionProjectionWithNoArrayRecursion() {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ParsedAggregationProjection::ProjectionPolicies noArrayRecursion;
+ noArrayRecursion.arrayRecursionPolicy =
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays;
+ return {expCtx, noArrayRecursion};
}
//
// Errors.
//
-DEATH_TEST(ExclusionProjection,
+DEATH_TEST(ExclusionProjectionExecutionTest,
ShouldRejectComputedField,
"Invariant failure fieldName[0] != '$'") {
// Top-level expression.
@@ -77,7 +91,7 @@ DEATH_TEST(ExclusionProjection,
exclusion.parse(BSON("a" << false << "b" << BSON("$literal" << 1)));
}
-DEATH_TEST(ExclusionProjection,
+DEATH_TEST(ExclusionProjectionExecutionTest,
ShouldFailWhenGivenIncludedNonIdField,
"Invariant failure !elem.trueValue() || elem.fieldNameStringData() == \"_id\"_sd") {
auto exclusion = makeExclusionProjectionWithDefaultPolicies();
@@ -91,12 +105,12 @@ DEATH_TEST(ExclusionProjectionExecutionTest,
exclusion.parse(BSON("_id.id1" << true));
}
-TEST(ExclusionProjection, ShouldAllowExplicitIdInclusionInExclusionSpec) {
+TEST(ExclusionProjectionExecutionTest, ShouldAllowExplicitIdInclusionInExclusionSpec) {
auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(BSON("_id" << true << "a" << false));
}
-TEST(ExclusionProjection, ShouldSerializeToEquivalentProjection) {
+TEST(ExclusionProjectionExecutionTest, ShouldSerializeToEquivalentProjection) {
auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(
fromjson("{a: 0, b: {c: NumberLong(0), d: 0.0}, 'x.y': false, _id: NumberInt(0)}"));
@@ -118,7 +132,7 @@ TEST(ExclusionProjection, ShouldSerializeToEquivalentProjection) {
ASSERT_VALUE_EQ(serialization["x"].getDocument()["y"], Value(false));
}
-TEST(ExclusionProjection, ShouldNotAddAnyDependencies) {
+TEST(ExclusionProjectionExecutionTest, ShouldNotAddAnyDependencies) {
// An exclusion projection will cause the stage to return DepsTracker::State::SEE_NEXT, meaning
// it doesn't strictly require any fields.
//
@@ -138,7 +152,7 @@ TEST(ExclusionProjection, ShouldNotAddAnyDependencies) {
ASSERT_FALSE(deps.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
-TEST(ExclusionProjection, ShouldReportExcludedFieldsAsModified) {
+TEST(ExclusionProjectionExecutionTest, ShouldReportExcludedFieldsAsModified) {
auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(BSON("_id" << false << "a" << false << "b.c" << false));
@@ -150,7 +164,8 @@ TEST(ExclusionProjection, ShouldReportExcludedFieldsAsModified) {
ASSERT_EQ(modifiedPaths.paths.size(), 3UL);
}
-TEST(ExclusionProjection, ShouldReportExcludedFieldsAsModifiedWhenSpecifiedAsNestedObj) {
+TEST(ExclusionProjectionExecutionTest,
+ ShouldReportExcludedFieldsAsModifiedWhenSpecifiedAsNestedObj) {
auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(BSON("a" << BSON("b" << false << "c" << BSON("d" << false))));
@@ -350,24 +365,8 @@ TEST(ExclusionProjectionExecutionTest, ShouldIncludeIdByDefault) {
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
-TEST(ExclusionProjectionExecutionTest, ShouldIncludeIdWithExplicitPolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
- exclusion.parse(BSON("a" << false));
-
- auto result = exclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
- auto expectedResult = Document{{"_id", 2}};
-
- ASSERT_DOCUMENT_EQ(result, expectedResult);
-}
-
TEST(ExclusionProjectionExecutionTest, ShouldExcludeIdWithExplicitPolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithDefaultIdExclusion();
exclusion.parse(BSON("a" << false));
auto result = exclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -377,10 +376,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldExcludeIdWithExplicitPolicy) {
}
TEST(ExclusionProjectionExecutionTest, ShouldOverrideIncludePolicyWithExplicitExcludeIdSpec) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(BSON("_id" << false << "a" << false));
auto result = exclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -390,10 +386,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldOverrideIncludePolicyWithExplicitEx
}
TEST(ExclusionProjectionExecutionTest, ShouldOverrideExcludePolicyWithExplicitIncludeIdSpec) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithDefaultIdExclusion();
exclusion.parse(BSON("_id" << true << "a" << false));
auto result = exclusion.applyProjection(Document{{"_id", 2}, {"a", 3}, {"b", 4}});
@@ -403,10 +396,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldOverrideExcludePolicyWithExplicitIn
}
TEST(ExclusionProjectionExecutionTest, ShouldAllowExclusionOfIdSubfieldWithDefaultIncludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies();
exclusion.parse(BSON("_id.id1" << false << "a" << false));
auto result = exclusion.applyProjection(
@@ -417,10 +407,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldAllowExclusionOfIdSubfieldWithDefau
}
TEST(ExclusionProjectionExecutionTest, ShouldAllowExclusionOfIdSubfieldWithDefaultExcludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithDefaultIdExclusion();
exclusion.parse(BSON("_id.id1" << false << "a" << false));
auto result = exclusion.applyProjection(
@@ -455,38 +442,12 @@ TEST(ExclusionProjectionExecutionTest, ShouldRecurseNestedArraysByDefault) {
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
-TEST(ExclusionProjectionExecutionTest, ShouldRecurseNestedArraysForExplicitProRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
- exclusion.parse(BSON("a.b" << false));
-
- // {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {a: [1, {c: 3}, [{c: 5}], {d: 6}]}
- auto result = exclusion.applyProjection(
- Document{{"a",
- vector<Value>{Value(1),
- Value(Document{{"b", 2}, {"c", 3}}),
- Value(vector<Value>{Value(Document{{"b", 4}, {"c", 5}})}),
- Value(Document{{"d", 6}})}}});
-
- auto expectedResult = Document{{"a",
- vector<Value>{Value(1),
- Value(Document{{"c", 3}}),
- Value(vector<Value>{Value(Document{{"c", 5}})}),
- Value(Document{{"d", 6}})}}};
-
- ASSERT_DOCUMENT_EQ(result, expectedResult);
-}
-
TEST(ExclusionProjectionExecutionTest, ShouldNotRecurseNestedArraysForNoRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithNoArrayRecursion();
exclusion.parse(BSON("a.b" << false));
- // {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {a: [1, {c: 3}, [{b: 4, c: 5}], {d: 6}]}
+ // {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {a: [1, {c: 3}, [{b: 4, c: 5}], {d:
+ // 6}]}
auto result = exclusion.applyProjection(
Document{{"a",
vector<Value>{Value(1),
@@ -505,10 +466,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldNotRecurseNestedArraysForNoRecurseP
}
TEST(ExclusionProjectionExecutionTest, ShouldNotRetainNestedArraysIfNoRecursionNeeded) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedExclusionProjection exclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ auto exclusion = makeExclusionProjectionWithNoArrayRecursion();
exclusion.parse(BSON("a" << false));
// {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {}
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
index a6a5f0c4ddd..57d0df56759 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
@@ -38,56 +38,19 @@ namespace mongo {
namespace parsed_aggregation_projection {
-using std::string;
-using std::unique_ptr;
-
//
// InclusionNode
//
-InclusionNode::InclusionNode(ProjectionArrayRecursionPolicy recursionPolicy, std::string pathToNode)
- : _arrayRecursionPolicy(recursionPolicy), _pathToNode(std::move(pathToNode)) {}
-
-void InclusionNode::optimize() {
- for (auto&& expressionIt : _expressions) {
- _expressions[expressionIt.first] = expressionIt.second->optimize();
- }
- for (auto&& childPair : _children) {
- childPair.second->optimize();
- }
-}
-
-void InclusionNode::serialize(MutableDocument* output,
- boost::optional<ExplainOptions::Verbosity> explain) const {
- // Always put "_id" first if it was included (implicitly or explicitly).
- if (_inclusions.find("_id") != _inclusions.end()) {
- output->addField("_id", Value(true));
- }
-
- for (auto&& includedField : _inclusions) {
- if (includedField == "_id") {
- // Handled above.
- continue;
- }
- output->addField(includedField, Value(true));
- }
+InclusionNode::InclusionNode(ProjectionPolicies policies, std::string pathToNode)
+ : ProjectionNode(policies, std::move(pathToNode)) {}
- for (auto&& field : _orderToProcessAdditionsAndChildren) {
- auto childIt = _children.find(field);
- if (childIt != _children.end()) {
- MutableDocument subDoc;
- childIt->second->serialize(&subDoc, explain);
- output->addField(field, subDoc.freezeToValue());
- } else {
- auto expressionIt = _expressions.find(field);
- invariant(expressionIt != _expressions.end());
- output->addField(field, expressionIt->second->serialize(static_cast<bool>(explain)));
- }
- }
+InclusionNode* InclusionNode::addOrGetChild(const std::string& field) {
+ return static_cast<InclusionNode*>(ProjectionNode::addOrGetChild(field));
}
-void InclusionNode::addDependencies(DepsTracker* deps) const {
- for (auto&& includedField : _inclusions) {
+void InclusionNode::reportDependencies(DepsTracker* deps) const {
+ for (auto&& includedField : _projectedFields) {
deps->fields.insert(FieldPath::getFullyQualifiedPath(_pathToNode, includedField));
}
@@ -102,166 +65,7 @@ void InclusionNode::addDependencies(DepsTracker* deps) const {
expressionPair.second->addDependencies(deps);
}
for (auto&& childPair : _children) {
- childPair.second->addDependencies(deps);
- }
-}
-
-void InclusionNode::applyInclusions(const Document& inputDoc, MutableDocument* outputDoc) const {
- auto it = inputDoc.fieldIterator();
- while (it.more()) {
- auto fieldPair = it.next();
- auto fieldName = fieldPair.first.toString();
- if (_inclusions.find(fieldName) != _inclusions.end()) {
- outputDoc->addField(fieldName, fieldPair.second);
- continue;
- }
-
- auto childIt = _children.find(fieldName);
- if (childIt != _children.end()) {
- outputDoc->addField(fieldName,
- childIt->second->applyInclusionsToValue(fieldPair.second));
- }
- }
-}
-
-Value InclusionNode::applyInclusionsToValue(Value inputValue) const {
- if (inputValue.getType() == BSONType::Object) {
- MutableDocument output;
- applyInclusions(inputValue.getDocument(), &output);
- return output.freezeToValue();
- } else if (inputValue.getType() == BSONType::Array) {
- std::vector<Value> values = inputValue.getArray();
- for (auto it = values.begin(); it != values.end(); ++it) {
- // If this is a nested array and our policy is to not recurse, remove the array.
- // Otherwise, descend into the array and project each element individually.
- const bool shouldSkip = it->isArray() &&
- _arrayRecursionPolicy == ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays;
- *it = (shouldSkip ? Value() : applyInclusionsToValue(*it));
- }
- return Value(std::move(values));
- } else {
- // This represents the case where we are including children of a field which does not have
- // any children. e.g. applying the projection {"a.b": true} to the document {a: 2}. It is
- // somewhat weird, but our semantics are to return a document without the field "a". To do
- // so, we return the "missing" value here.
- return Value();
- }
-}
-
-void InclusionNode::addComputedFields(MutableDocument* outputDoc, const Document& root) const {
- for (auto&& field : _orderToProcessAdditionsAndChildren) {
- auto childIt = _children.find(field);
- if (childIt != _children.end()) {
- outputDoc->setField(field,
- childIt->second->addComputedFields(outputDoc->peek()[field], root));
- } else {
- auto expressionIt = _expressions.find(field);
- invariant(expressionIt != _expressions.end());
- outputDoc->setField(field, expressionIt->second->evaluate(root));
- }
- }
-}
-
-Value InclusionNode::addComputedFields(Value inputValue, const Document& root) const {
- if (inputValue.getType() == BSONType::Object) {
- MutableDocument outputDoc(inputValue.getDocument());
- addComputedFields(&outputDoc, root);
- return outputDoc.freezeToValue();
- } else if (inputValue.getType() == BSONType::Array) {
- std::vector<Value> values = inputValue.getArray();
- for (auto it = values.begin(); it != values.end(); ++it) {
- *it = addComputedFields(*it, root);
- }
- return Value(std::move(values));
- } else {
- if (subtreeContainsComputedFields()) {
- // Our semantics in this case are to replace whatever existing value we find with a new
- // document of all the computed values. This case represents applying a projection like
- // {"a.b": {$literal: 1}} to the document {a: 1}. This should yield {a: {b: 1}}.
- MutableDocument outputDoc;
- addComputedFields(&outputDoc, root);
- return outputDoc.freezeToValue();
- }
- // We didn't have any expressions, so just return the missing value.
- return Value();
- }
-}
-
-bool InclusionNode::subtreeContainsComputedFields() const {
- return (!_expressions.empty()) ||
- std::any_of(
- _children.begin(),
- _children.end(),
- [](const std::pair<const std::string, std::unique_ptr<InclusionNode>>& childPair) {
- return childPair.second->subtreeContainsComputedFields();
- });
-}
-
-void InclusionNode::addComputedField(const FieldPath& path, boost::intrusive_ptr<Expression> expr) {
- if (path.getPathLength() == 1) {
- auto fieldName = path.fullPath();
- _expressions[fieldName] = expr;
- _orderToProcessAdditionsAndChildren.push_back(fieldName);
- return;
- }
- addOrGetChild(path.getFieldName(0).toString())->addComputedField(path.tail(), expr);
-}
-
-void InclusionNode::addIncludedField(const FieldPath& path) {
- if (path.getPathLength() == 1) {
- _inclusions.insert(path.fullPath());
- return;
- }
- addOrGetChild(path.getFieldName(0).toString())->addIncludedField(path.tail());
-}
-
-InclusionNode* InclusionNode::addOrGetChild(std::string field) {
- auto child = getChild(field);
- return child ? child : addChild(field);
-}
-
-InclusionNode* InclusionNode::getChild(string field) const {
- auto childIt = _children.find(field);
- return childIt == _children.end() ? nullptr : childIt->second.get();
-}
-
-InclusionNode* InclusionNode::addChild(string field) {
- invariant(!str::contains(field, "."));
- _orderToProcessAdditionsAndChildren.push_back(field);
- auto childPath = FieldPath::getFullyQualifiedPath(_pathToNode, field);
- auto insertedPair = _children.emplace(std::make_pair(
- std::move(field),
- stdx::make_unique<InclusionNode>(_arrayRecursionPolicy, std::move(childPath))));
- return insertedPair.first->second.get();
-}
-
-void InclusionNode::addPreservedPaths(std::set<std::string>* preservedPaths) const {
- // Only our inclusion paths are preserved. This inclusion node may also have paths with
- // associated expressions, but those paths are modified and therefore are not considered
- // "preserved".
- for (auto&& includedField : _inclusions) {
- preservedPaths->insert(FieldPath::getFullyQualifiedPath(_pathToNode, includedField));
- }
- for (auto&& childPair : _children) {
- childPair.second->addPreservedPaths(preservedPaths);
- }
-}
-
-void InclusionNode::addComputedPaths(std::set<std::string>* computedPaths,
- StringMap<std::string>* renamedPaths) const {
- for (auto&& computedPair : _expressions) {
- // The expression's path is the concatenation of the path to this inclusion node,
- // plus the field name associated with the expression.
- auto exprPath = FieldPath::getFullyQualifiedPath(_pathToNode, computedPair.first);
- auto exprComputedPaths = computedPair.second->getComputedPaths(exprPath);
- computedPaths->insert(exprComputedPaths.paths.begin(), exprComputedPaths.paths.end());
-
- for (auto&& rename : exprComputedPaths.renames) {
- (*renamedPaths)[rename.first] = rename.second;
- }
- }
- for (auto&& childPair : _children) {
- childPair.second->addComputedPaths(computedPaths, renamedPaths);
+ childPair.second->reportDependencies(deps);
}
}
@@ -300,7 +104,7 @@ void ParsedInclusionProjection::parse(const BSONObj& spec) {
case BSONType::NumberDecimal: {
// This is an inclusion specification.
invariant(elem.trueValue());
- _root->addIncludedField(FieldPath(elem.fieldName()));
+ _root->addProjectionForPath(FieldPath(elem.fieldName()));
break;
}
case BSONType::Object: {
@@ -313,7 +117,7 @@ void ParsedInclusionProjection::parse(const BSONObj& spec) {
// The field name might be a dotted path. If so, we need to keep adding children
// to our tree until we create a child that represents that path.
auto remainingPath = FieldPath(elem.fieldName());
- auto child = _root.get();
+ auto* child = _root.get();
while (remainingPath.getPathLength() > 1) {
child = child->addOrGetChild(remainingPath.getFieldName(0).toString());
remainingPath = remainingPath.tail();
@@ -327,7 +131,7 @@ void ParsedInclusionProjection::parse(const BSONObj& spec) {
}
default: {
// This is a literal value.
- _root->addComputedField(
+ _root->addExpressionForPath(
FieldPath(elem.fieldName()),
Expression::parseOperand(_expCtx, elem, _expCtx->variablesParseState));
}
@@ -336,11 +140,11 @@ void ParsedInclusionProjection::parse(const BSONObj& spec) {
if (!idSpecified) {
// _id wasn't specified, so apply the default _id projection policy here.
- if (_defaultIdPolicy == ProjectionDefaultIdPolicy::kExcludeId) {
+ if (_policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kExcludeId) {
_idExcluded = true;
} else {
atLeastOneFieldInOutput = true;
- _root->addIncludedField(FieldPath("_id"));
+ _root->addProjectionForPath(FieldPath("_id"));
}
}
@@ -352,13 +156,7 @@ void ParsedInclusionProjection::parse(const BSONObj& spec) {
Document ParsedInclusionProjection::applyProjection(const Document& inputDoc) const {
// All expressions will be evaluated in the context of the input document, before any
// transformations have been applied.
- MutableDocument output;
- _root->applyInclusions(inputDoc, &output);
- _root->addComputedFields(&output, inputDoc);
-
- // Always pass through the metadata.
- output.copyMetaDataFrom(inputDoc);
- return output.freeze();
+ return _root->applyToDocument(inputDoc);
}
bool ParsedInclusionProjection::parseObjectAsExpression(
@@ -369,8 +167,8 @@ bool ParsedInclusionProjection::parseObjectAsExpression(
// This is an expression like {$add: [...]}. We have already verified that it has only one
// field.
invariant(objSpec.nFields() == 1);
- _root->addComputedField(pathToObject,
- Expression::parseExpression(_expCtx, objSpec, variablesParseState));
+ _root->addExpressionForPath(
+ pathToObject, Expression::parseExpression(_expCtx, objSpec, variablesParseState));
return true;
}
return false;
@@ -393,7 +191,7 @@ void ParsedInclusionProjection::parseSubObject(const BSONObj& subObj,
case BSONType::NumberDecimal: {
// This is an inclusion specification.
invariant(elem.trueValue());
- node->addIncludedField(FieldPath(elem.fieldName()));
+ node->addProjectionForPath(FieldPath(elem.fieldName()));
break;
}
case BSONType::Object: {
@@ -405,13 +203,13 @@ void ParsedInclusionProjection::parseSubObject(const BSONObj& subObj,
variablesParseState)) {
break;
}
- auto child = node->addOrGetChild(fieldName);
+ auto* child = node->addOrGetChild(fieldName);
parseSubObject(elem.Obj(), variablesParseState, child);
break;
}
default: {
// This is a literal value.
- node->addComputedField(
+ node->addExpressionForPath(
FieldPath(elem.fieldName()),
Expression::parseOperand(_expCtx, elem, variablesParseState));
}
@@ -421,7 +219,7 @@ void ParsedInclusionProjection::parseSubObject(const BSONObj& subObj,
bool ParsedInclusionProjection::isSubsetOfProjection(const BSONObj& proj) const {
std::set<std::string> preservedPaths;
- _root->addPreservedPaths(&preservedPaths);
+ _root->reportProjectedPaths(&preservedPaths);
for (auto&& includedField : preservedPaths) {
if (!proj.hasField(includedField))
return false;
@@ -430,7 +228,7 @@ bool ParsedInclusionProjection::isSubsetOfProjection(const BSONObj& proj) const
// If the inclusion has any computed fields or renamed fields, then it's not a subset.
std::set<std::string> computedPaths;
StringMap<std::string> renamedPaths;
- _root->addComputedPaths(&computedPaths, &renamedPaths);
+ _root->reportComputedPaths(&computedPaths, &renamedPaths);
return computedPaths.empty() && renamedPaths.empty();
}
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.h b/src/mongo/db/pipeline/parsed_inclusion_projection.h
index 07f7afa80d3..bd11c3e638c 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.h
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.h
@@ -35,6 +35,7 @@
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/parsed_aggregation_projection.h"
+#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
#include "mongo/stdx/memory.h"
#include "mongo/stdx/unordered_map.h"
#include "mongo/stdx/unordered_set.h"
@@ -52,134 +53,28 @@ namespace parsed_aggregation_projection {
* level inclusions or additions, with any child InclusionNodes representing dotted or nested
* inclusions or additions.
*/
-class InclusionNode {
+class InclusionNode final : public ProjectionNode {
public:
- using ProjectionArrayRecursionPolicy =
- ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
+ InclusionNode(ProjectionPolicies policies, std::string pathToNode = "");
- InclusionNode(ProjectionArrayRecursionPolicy recursionPolicy, std::string pathToNode = "");
+ InclusionNode* addOrGetChild(const std::string& field);
- /**
- * Optimize any computed expressions.
- */
- void optimize();
-
- /**
- * Serialize this projection.
- */
- void serialize(MutableDocument* output,
- boost::optional<ExplainOptions::Verbosity> explain) const;
-
- /**
- * Adds dependencies of any fields that need to be included, or that are used by any
- * expressions.
- */
- void addDependencies(DepsTracker* deps) const;
-
- /**
- * Loops over 'inputDoc', extracting and appending any included fields into 'outputDoc'. This
- * will also copy over enough information to preserve the structure of the incoming document for
- * all the fields this projection cares about.
- *
- * For example, given an InclusionNode tree representing this projection:
- * {a: {b: 1, c: <exp>}, "d.e": <exp>}
- * calling applyInclusions() with an 'inputDoc' of
- * {a: [{b: 1, d: 1}, {b: 2, d: 2}], d: [{e: 1, f: 1}, {e: 1, f: 1}]}
- * and an empty 'outputDoc' will leave 'outputDoc' representing the document
- * {a: [{b: 1}, {b: 2}], d: [{}, {}]}.
- */
- void applyInclusions(const Document& inputDoc, MutableDocument* outputDoc) const;
-
- /**
- * Add computed fields to 'outputDoc'.
- */
- void addComputedFields(MutableDocument* outputDoc, const Document& root) const;
-
- /**
- * Creates the child if it doesn't already exist. 'field' is not allowed to be dotted.
- */
- InclusionNode* addOrGetChild(std::string field);
-
- /**
- * Recursively adds 'path' into the tree as a computed field, creating any child nodes if
- * necessary.
- *
- * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in
- * the tree. For example, it is an error to add the path "a.b" as a computed field to a tree
- * which has already included the field "a".
- */
- void addComputedField(const FieldPath& path, boost::intrusive_ptr<Expression> expr);
-
- /**
- * Recursively adds 'path' into the tree as an included field, creating any child nodes if
- * necessary.
- *
- * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in
- * the tree. For example, it is an error to include the path "a.b" from a tree which has already
- * added a computed field "a".
- */
- void addIncludedField(const FieldPath& path);
+ void reportDependencies(DepsTracker* deps) const final;
- std::string getPath() const {
- return _pathToNode;
+protected:
+ std::unique_ptr<ProjectionNode> makeChild(std::string fieldName) const final {
+ return std::make_unique<InclusionNode>(
+ _policies, FieldPath::getFullyQualifiedPath(_pathToNode, fieldName));
+ }
+ Document initializeOutputDocument(const Document& inputDoc) const final {
+ return {};
+ }
+ Value applyLeafProjectionToValue(const Value& value) const final {
+ return value;
+ }
+ Value transformSkippedValueForOutput(const Value& value) const final {
+ return Value();
}
-
- /**
- * Recursively add all paths that are preserved by this inclusion projection.
- */
- void addPreservedPaths(std::set<std::string>* preservedPaths) const;
-
- /**
- * Recursively adds all paths that are purely computed in this inclusion projection to
- * 'computedPaths'.
- *
- * Computed paths that are identified as the result of a simple rename are instead filled out in
- * 'renamedPaths'. Each entry in 'renamedPaths' maps from the path's new name to its old name
- * prior to application of this inclusion projection.
- */
- void addComputedPaths(std::set<std::string>* computedPaths,
- StringMap<std::string>* renamedPaths) const;
-
-private:
- // Helpers for the Document versions above. These will apply the transformation recursively to
- // each element of any arrays, and ensure non-documents are handled appropriately.
- Value applyInclusionsToValue(Value inputVal) const;
- Value addComputedFields(Value inputVal, const Document& root) const;
-
- /**
- * Returns nullptr if no such child exists.
- */
- InclusionNode* getChild(std::string field) const;
-
- /**
- * Adds a new InclusionNode as a child. 'field' cannot be dotted.
- */
- InclusionNode* addChild(std::string field);
-
- /**
- * Returns true if this node or any child of this node contains a computed field.
- */
- bool subtreeContainsComputedFields() const;
-
- ProjectionArrayRecursionPolicy _arrayRecursionPolicy;
-
- std::string _pathToNode;
-
- // Our projection semantics are such that all field additions need to be processed in the order
- // specified. '_orderToProcessAdditionsAndChildren' tracks that order.
- //
- // For example, for the specification {a: <expression>, "b.c": <expression>, d: <expression>},
- // we need to add the top level fields in the order "a", then "b", then "d". This ordering
- // information needs to be tracked separately, since "a" and "d" will be tracked via
- // '_expressions', and "b.c" will be tracked as a child InclusionNode in '_children'. For the
- // example above, '_orderToProcessAdditionsAndChildren' would be ["a", "b", "d"].
- std::vector<std::string> _orderToProcessAdditionsAndChildren;
-
- StringMap<boost::intrusive_ptr<Expression>> _expressions;
- stdx::unordered_set<std::string> _inclusions;
-
- // TODO use StringMap once SERVER-23700 is resolved.
- stdx::unordered_map<std::string, std::unique_ptr<InclusionNode>> _children;
};
/**
@@ -192,10 +87,8 @@ private:
class ParsedInclusionProjection : public ParsedAggregationProjection {
public:
ParsedInclusionProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionDefaultIdPolicy defaultIdPolicy,
- ProjectionArrayRecursionPolicy arrayRecursionPolicy)
- : ParsedAggregationProjection(expCtx, defaultIdPolicy, arrayRecursionPolicy),
- _root(new InclusionNode(_arrayRecursionPolicy)) {}
+ ProjectionPolicies policies)
+ : ParsedAggregationProjection(expCtx, policies), _root(new InclusionNode(policies)) {}
TransformerType getType() const final {
return TransformerType::kInclusionProjection;
@@ -215,7 +108,7 @@ public:
if (_idExcluded) {
output.addField("_id", Value(false));
}
- _root->serialize(&output, explain);
+ _root->serialize(explain, &output);
return output.freeze();
}
@@ -227,17 +120,17 @@ public:
}
DepsTracker::State addDependencies(DepsTracker* deps) const final {
- _root->addDependencies(deps);
+ _root->reportDependencies(deps);
return DepsTracker::State::EXHAUSTIVE_FIELDS;
}
DocumentSource::GetModPathsReturn getModifiedPaths() const final {
std::set<std::string> preservedPaths;
- _root->addPreservedPaths(&preservedPaths);
+ _root->reportProjectedPaths(&preservedPaths);
std::set<std::string> computedPaths;
StringMap<std::string> renamedPaths;
- _root->addComputedPaths(&computedPaths, &renamedPaths);
+ _root->reportComputedPaths(&computedPaths, &renamedPaths);
return {DocumentSource::GetModPathsReturn::Type::kAllExcept,
std::move(preservedPaths),
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
index 9327be98517..91e85cfad37 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
@@ -49,8 +49,7 @@ namespace mongo {
namespace parsed_aggregation_projection {
namespace {
-using ProjectionArrayRecursionPolicy = ParsedAggregationProjection::ProjectionArrayRecursionPolicy;
-using ProjectionDefaultIdPolicy = ParsedAggregationProjection::ProjectionDefaultIdPolicy;
+using ProjectionPolicies = ParsedAggregationProjection::ProjectionPolicies;
using std::vector;
@@ -59,42 +58,57 @@ BSONObj wrapInLiteral(const T& arg) {
return BSON("$literal" << arg);
}
-// Helper to simplify the creation of a ParsedInclusionProjection which includes _id and recurses
-// nested arrays by default.
+// Helper to simplify the creation of a ParsedInclusionProjection with default policies.
ParsedInclusionProjection makeInclusionProjectionWithDefaultPolicies() {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- return ParsedInclusionProjection(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ ParsedAggregationProjection::ProjectionPolicies defaultPolicies;
+ return {expCtx, defaultPolicies};
}
-DEATH_TEST(InclusionProjection,
+// Helper to simplify the creation of a ParsedInclusionProjection which excludes _id by default.
+ParsedInclusionProjection makeInclusionProjectionWithDefaultIdExclusion() {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ParsedAggregationProjection::ProjectionPolicies defaultExcludeId;
+ defaultExcludeId.idPolicy = ProjectionPolicies::DefaultIdPolicy::kExcludeId;
+ return {expCtx, defaultExcludeId};
+}
+
+// Helper to simplify the creation of a ParsedInclusionProjection which does not recurse arrays.
+ParsedInclusionProjection makeInclusionProjectionWithNoArrayRecursion() {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ParsedAggregationProjection::ProjectionPolicies noArrayRecursion;
+ noArrayRecursion.arrayRecursionPolicy =
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays;
+ return {expCtx, noArrayRecursion};
+}
+
+DEATH_TEST(InclusionProjectionExecutionTest,
ShouldFailWhenGivenExcludedNonIdField,
"Invariant failure elem.trueValue()") {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << false));
}
-DEATH_TEST(InclusionProjection,
+DEATH_TEST(InclusionProjectionExecutionTest,
ShouldFailWhenGivenIncludedIdSubfield,
"Invariant failure elem.trueValue()") {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id.id1" << false));
}
-TEST(InclusionProjection, ShouldThrowWhenParsingInvalidExpression) {
+TEST(InclusionProjectionExecutionTest, ShouldThrowWhenParsingInvalidExpression) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
ASSERT_THROWS(inclusion.parse(BSON("a" << BSON("$gt" << BSON("bad"
<< "arguments")))),
AssertionException);
}
-TEST(InclusionProjection, ShouldRejectProjectionWithNoOutputFields) {
+TEST(InclusionProjectionExecutionTest, ShouldRejectProjectionWithNoOutputFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
ASSERT_THROWS(inclusion.parse(BSON("_id" << false)), AssertionException);
}
-TEST(InclusionProjection, ShouldAddIncludedFieldsToDependencies) {
+TEST(InclusionProjectionExecutionTest, ShouldAddIncludedFieldsToDependencies) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id" << false << "a" << true << "x.y" << true));
@@ -107,7 +121,7 @@ TEST(InclusionProjection, ShouldAddIncludedFieldsToDependencies) {
ASSERT_EQ(deps.fields.count("x.y"), 1UL);
}
-TEST(InclusionProjection, ShouldAddIdToDependenciesIfNotSpecified) {
+TEST(InclusionProjectionExecutionTest, ShouldAddIdToDependenciesIfNotSpecified) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true));
@@ -119,7 +133,7 @@ TEST(InclusionProjection, ShouldAddIdToDependenciesIfNotSpecified) {
ASSERT_EQ(deps.fields.count("a"), 1UL);
}
-TEST(InclusionProjection, ShouldAddDependenciesOfComputedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldAddDependenciesOfComputedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a"
<< "$a"
@@ -135,7 +149,7 @@ TEST(InclusionProjection, ShouldAddDependenciesOfComputedFields) {
ASSERT_EQ(deps.fields.count("z"), 1UL);
}
-TEST(InclusionProjection, ShouldAddPathToDependenciesForNestedComputedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldAddPathToDependenciesForNestedComputedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("x.y"
<< "$z"));
@@ -152,7 +166,7 @@ TEST(InclusionProjection, ShouldAddPathToDependenciesForNestedComputedFields) {
ASSERT_EQ(deps.fields.count("x"), 1UL);
}
-TEST(InclusionProjection, ShouldSerializeToEquivalentProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldSerializeToEquivalentProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(fromjson("{a: {$add: ['$a', 2]}, b: {d: 3}, 'x.y': {$literal: 4}}"));
@@ -170,7 +184,7 @@ TEST(InclusionProjection, ShouldSerializeToEquivalentProjection) {
inclusion.serializeTransformation(ExplainOptions::Verbosity::kExecAllPlans));
}
-TEST(InclusionProjection, ShouldSerializeExplicitExclusionOfId) {
+TEST(InclusionProjectionExecutionTest, ShouldSerializeExplicitExclusionOfId) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id" << false << "a" << true));
@@ -188,7 +202,7 @@ TEST(InclusionProjection, ShouldSerializeExplicitExclusionOfId) {
}
-TEST(InclusionProjection, ShouldOptimizeTopLevelExpressions) {
+TEST(InclusionProjectionExecutionTest, ShouldOptimizeTopLevelExpressions) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << BSON("$add" << BSON_ARRAY(1 << 2))));
@@ -206,7 +220,7 @@ TEST(InclusionProjection, ShouldOptimizeTopLevelExpressions) {
inclusion.serializeTransformation(ExplainOptions::Verbosity::kExecAllPlans));
}
-TEST(InclusionProjection, ShouldOptimizeNestedExpressions) {
+TEST(InclusionProjectionExecutionTest, ShouldOptimizeNestedExpressions) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << BSON("$add" << BSON_ARRAY(1 << 2))));
@@ -225,7 +239,7 @@ TEST(InclusionProjection, ShouldOptimizeNestedExpressions) {
inclusion.serializeTransformation(ExplainOptions::Verbosity::kExecAllPlans));
}
-TEST(InclusionProjection, ShouldReportThatAllExceptIncludedFieldsAreModified) {
+TEST(InclusionProjectionExecutionTest, ShouldReportThatAllExceptIncludedFieldsAreModified) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON(
"a" << wrapInLiteral("computedVal") << "b.c" << wrapInLiteral("computedVal") << "d" << true
@@ -244,7 +258,8 @@ TEST(InclusionProjection, ShouldReportThatAllExceptIncludedFieldsAreModified) {
ASSERT_EQ(modifiedPaths.paths.size(), 3UL);
}
-TEST(InclusionProjection, ShouldReportThatAllExceptIncludedFieldsAreModifiedWithIdExclusion) {
+TEST(InclusionProjectionExecutionTest,
+ ShouldReportThatAllExceptIncludedFieldsAreModifiedWithIdExclusion) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id" << false << "a" << wrapInLiteral("computedVal") << "b.c"
<< wrapInLiteral("computedVal")
@@ -618,6 +633,26 @@ TEST(InclusionProjectionExecutionTest, ComputedFieldReplacingExistingShouldAppea
}
//
+// Metadata inclusion.
+//
+
+TEST(InclusionProjectionExecutionTest, ShouldAlwaysKeepMetadataFromOriginalDoc) {
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
+ inclusion.parse(BSON("a" << true));
+
+ MutableDocument inputDocBuilder(Document{{"a", 1}});
+ inputDocBuilder.setRandMetaField(1.0);
+ inputDocBuilder.setTextScore(10.0);
+ Document inputDoc = inputDocBuilder.freeze();
+
+ auto result = inclusion.applyProjection(inputDoc);
+
+ MutableDocument expectedDoc(inputDoc);
+ expectedDoc.copyMetaDataFrom(inputDoc);
+ ASSERT_DOCUMENT_EQ(result, expectedDoc.freeze());
+}
+
+//
// _id inclusion policy.
//
@@ -632,10 +667,7 @@ TEST(InclusionProjectionExecutionTest, ShouldIncludeIdByDefault) {
}
TEST(InclusionProjectionExecutionTest, ShouldIncludeIdWithIncludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true));
auto result = inclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -645,10 +677,7 @@ TEST(InclusionProjectionExecutionTest, ShouldIncludeIdWithIncludePolicy) {
}
TEST(InclusionProjectionExecutionTest, ShouldExcludeIdWithExcludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultIdExclusion();
inclusion.parse(BSON("a" << true));
auto result = inclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -658,10 +687,7 @@ TEST(InclusionProjectionExecutionTest, ShouldExcludeIdWithExcludePolicy) {
}
TEST(InclusionProjectionExecutionTest, ShouldOverrideIncludePolicyWithExplicitExcludeIdSpec) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id" << false << "a" << true));
auto result = inclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -671,10 +697,7 @@ TEST(InclusionProjectionExecutionTest, ShouldOverrideIncludePolicyWithExplicitEx
}
TEST(InclusionProjectionExecutionTest, ShouldOverrideExcludePolicyWithExplicitIncludeIdSpec) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultIdExclusion();
inclusion.parse(BSON("_id" << true << "a" << true));
auto result = inclusion.applyProjection(Document{{"_id", 2}, {"a", 3}});
@@ -684,10 +707,7 @@ TEST(InclusionProjectionExecutionTest, ShouldOverrideExcludePolicyWithExplicitIn
}
TEST(InclusionProjectionExecutionTest, ShouldAllowInclusionOfIdSubfieldWithDefaultIncludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("_id.id1" << true << "a" << true));
auto result = inclusion.applyProjection(
@@ -698,10 +718,7 @@ TEST(InclusionProjectionExecutionTest, ShouldAllowInclusionOfIdSubfieldWithDefau
}
TEST(InclusionProjectionExecutionTest, ShouldAllowInclusionOfIdSubfieldWithDefaultExcludePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kExcludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultIdExclusion();
inclusion.parse(BSON("_id.id1" << true << "a" << true));
auto result = inclusion.applyProjection(
@@ -736,35 +753,8 @@ TEST(InclusionProjectionExecutionTest, ShouldRecurseNestedArraysByDefault) {
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
-TEST(InclusionProjectionExecutionTest, ShouldRecurseNestedArraysForExplicitProRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
- inclusion.parse(BSON("a.b" << true));
-
- // {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {a: [{b: 2}, [{b: 4}], {}]}
- auto result = inclusion.applyProjection(
- Document{{"a",
- vector<Value>{Value(1),
- Value(Document{{"b", 2}, {"c", 3}}),
- Value(vector<Value>{Value(Document{{"b", 4}, {"c", 5}})}),
- Value(Document{{"d", 6}})}}});
-
- auto expectedResult = Document{{"a",
- vector<Value>{Value(),
- Value(Document{{"b", 2}}),
- Value(vector<Value>{Value(Document{{"b", 4}})}),
- Value(Document{})}}};
-
- ASSERT_DOCUMENT_EQ(result, expectedResult);
-}
-
TEST(InclusionProjectionExecutionTest, ShouldNotRecurseNestedArraysForNoRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithNoArrayRecursion();
inclusion.parse(BSON("a.b" << true));
// {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => {a: [{b: 2}, {}]}
@@ -782,10 +772,7 @@ TEST(InclusionProjectionExecutionTest, ShouldNotRecurseNestedArraysForNoRecurseP
}
TEST(InclusionProjectionExecutionTest, ShouldRetainNestedArraysIfNoRecursionNeeded) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithNoArrayRecursion();
inclusion.parse(BSON("a" << true));
// {a: [1, {b: 2, c: 3}, [{b: 4, c: 5}], {d: 6}]} => [output doc identical to input]
@@ -803,10 +790,7 @@ TEST(InclusionProjectionExecutionTest, ShouldRetainNestedArraysIfNoRecursionNeed
}
TEST(InclusionProjectionExecutionTest, ComputedFieldIsAddedToNestedArrayElementsForRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << wrapInLiteral("COMPUTED")));
vector<Value> nestedValues = {Value(1),
@@ -829,10 +813,7 @@ TEST(InclusionProjectionExecutionTest, ComputedFieldIsAddedToNestedArrayElements
}
TEST(InclusionProjectionExecutionTest, ComputedFieldShouldReplaceNestedArrayForNoRecursePolicy) {
- const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedInclusionProjection inclusion(expCtx,
- ProjectionDefaultIdPolicy::kIncludeId,
- ProjectionArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ auto inclusion = makeInclusionProjectionWithNoArrayRecursion();
inclusion.parse(BSON("a.b" << wrapInLiteral("COMPUTED")));
// For kRecurseNestedArrays, the computed field (1) replaces any scalar values in the array with
@@ -859,30 +840,10 @@ TEST(InclusionProjectionExecutionTest, ComputedFieldShouldReplaceNestedArrayForN
}
//
-// Misc.
-//
-
-TEST(InclusionProjectionExecutionTest, ShouldAlwaysKeepMetadataFromOriginalDoc) {
- auto inclusion = makeInclusionProjectionWithDefaultPolicies();
- inclusion.parse(BSON("a" << true));
-
- MutableDocument inputDocBuilder(Document{{"a", 1}});
- inputDocBuilder.setRandMetaField(1.0);
- inputDocBuilder.setTextScore(10.0);
- Document inputDoc = inputDocBuilder.freeze();
-
- auto result = inclusion.applyProjection(inputDoc);
-
- MutableDocument expectedDoc(inputDoc);
- expectedDoc.copyMetaDataFrom(inputDoc);
- ASSERT_DOCUMENT_EQ(result, expectedDoc.freeze());
-}
-
-//
// Detection of subset projection.
//
-TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForIdenticalProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForIdenticalProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
@@ -891,7 +852,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForIdenticalProjection) {
ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForSupersetProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForSupersetProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
@@ -900,7 +861,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForSupersetProjection) {
ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForIdenticalNestedProjection) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForIdenticalNestedProjection) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << true));
@@ -909,7 +870,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForIdenticalNestedProjecti
ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForSupersetProjectionWithNestedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectSubsetForSupersetProjectionWithNestedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "c" << BSON("d" << true)));
@@ -918,7 +879,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectSubsetForSupersetProjectionWithN
ASSERT_TRUE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithMissingFields) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true));
@@ -929,7 +890,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithMissin
ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest,
+TEST(InclusionProjectionExecutionTest,
ShouldDetectNonSubsetForSupersetProjectionWithoutComputedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true << "b" << true << "c" << BSON("$literal" << 1)));
@@ -939,7 +900,7 @@ TEST(InclusionProjectionSubsetTest,
ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithMissingNestedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingNestedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a.b" << true << "a.c" << true));
@@ -948,7 +909,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithMissin
ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithRenamedFields) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithRenamedFields) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a"
<< "$b"));
@@ -958,7 +919,7 @@ TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithRename
ASSERT_FALSE(inclusion.isSubsetOfProjection(proj));
}
-TEST(InclusionProjectionSubsetTest, ShouldDetectNonSubsetForProjectionWithMissingIdField) {
+TEST(InclusionProjectionExecutionTest, ShouldDetectNonSubsetForProjectionWithMissingIdField) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies();
inclusion.parse(BSON("a" << true));