summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
diff options
context:
space:
mode:
authorCharlie Swanson <charlie.swanson@mongodb.com>2016-06-21 13:47:18 -0400
committerCharlie Swanson <charlie.swanson@mongodb.com>2016-06-24 18:07:36 -0400
commit752eb0f448987b3e4183beacc622f4e95e7803be (patch)
tree525ab64b7e0ae8a5e4fba3da7feefd34e2600581 /src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
parent7e3ce9cec306df5abb15c0c1a599023270cfb807 (diff)
downloadmongo-752eb0f448987b3e4183beacc622f4e95e7803be.tar.gz
SERVER-18966 Add the ability to exclude fields in $project stage
Diffstat (limited to 'src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp')
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp545
1 files changed, 545 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
new file mode 100644
index 00000000000..f4c1d6a4a0e
--- /dev/null
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
@@ -0,0 +1,545 @@
+/**
+ * Copyright (C) 2016 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_inclusion_projection.h"
+
+#include <vector>
+
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/pipeline/dependencies.h"
+#include "mongo/db/pipeline/document.h"
+#include "mongo/db/pipeline/value.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace parsed_aggregation_projection {
+namespace {
+using std::vector;
+
+template <typename T>
+BSONObj wrapInLiteral(const T& arg) {
+ return BSON("$literal" << arg);
+}
+
+TEST(InclusionProjection, ShouldThrowWhenParsingInvalidExpression) {
+ ParsedInclusionProjection inclusion;
+ ASSERT_THROWS(inclusion.parse(BSON("a" << BSON("$gt" << BSON("bad"
+ << "arguments")))),
+ UserException);
+}
+
+TEST(InclusionProjection, ShouldRejectProjectionWithNoOutputFields) {
+ ParsedInclusionProjection inclusion;
+ ASSERT_THROWS(inclusion.parse(BSON("_id" << false)), UserException);
+}
+
+TEST(InclusionProjection, ShouldAddIncludedFieldsToDependencies) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("_id" << false << "a" << true << "x.y" << true));
+
+ DepsTracker deps;
+ inclusion.addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 2UL);
+ ASSERT_EQ(deps.fields.count("_id"), 0UL);
+ ASSERT_EQ(deps.fields.count("a"), 1UL);
+ ASSERT_EQ(deps.fields.count("x.y"), 1UL);
+}
+
+TEST(InclusionProjection, ShouldAddIdToDependenciesIfNotSpecified) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true));
+
+ DepsTracker deps;
+ inclusion.addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 2UL);
+ ASSERT_EQ(deps.fields.count("_id"), 1UL);
+ ASSERT_EQ(deps.fields.count("a"), 1UL);
+}
+
+TEST(InclusionProjection, ShouldAddDependenciesOfComputedFields) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a"
+ << "$a"
+ << "x"
+ << "$z"));
+
+ DepsTracker deps;
+ inclusion.addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 3UL);
+ ASSERT_EQ(deps.fields.count("_id"), 1UL);
+ ASSERT_EQ(deps.fields.count("a"), 1UL);
+ ASSERT_EQ(deps.fields.count("z"), 1UL);
+}
+
+TEST(InclusionProjection, ShouldAddPathToDependenciesForNestedComputedFields) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("x.y"
+ << "$z"));
+
+ DepsTracker deps;
+ inclusion.addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 3UL);
+ // Implicit "_id".
+ ASSERT_EQ(deps.fields.count("_id"), 1UL);
+ // Needed by the ExpressionFieldPath.
+ ASSERT_EQ(deps.fields.count("z"), 1UL);
+ // Needed to ensure we preserve the structure of the input document.
+ ASSERT_EQ(deps.fields.count("x"), 1UL);
+}
+
+TEST(InclusionProjection, ShouldSerializeToEquivalentProjection) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(fromjson("{a: {$add: ['$a', 2]}, b: {d: 3}, 'x.y': {$literal: 4}}"));
+
+ // Adds implicit "_id" inclusion, converts numbers to bools, serializes expressions.
+ auto expectedSerialization = Document(fromjson(
+ "{_id: true, a: {$add: [\"$a\", {$const: 2}]}, b: {d: true}, x: {y: {$const: 4}}}"));
+
+ // Should be the same if we're serializing for explain or for internal use.
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(false));
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(true));
+}
+
+TEST(InclusionProjection, ShouldSerializeExplicitExclusionOfId) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("_id" << false << "a" << true));
+
+ // Adds implicit "_id" inclusion, converts numbers to bools, serializes expressions.
+ auto expectedSerialization = Document{{"_id", false}, {"a", true}};
+
+ // Should be the same if we're serializing for explain or for internal use.
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(false));
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(true));
+}
+
+
+TEST(InclusionProjection, ShouldOptimizeTopLevelExpressions) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << BSON("$add" << BSON_ARRAY(1 << 2))));
+
+ inclusion.optimize();
+
+ auto expectedSerialization = Document{{"_id", true}, {"a", Document{{"$const", 3}}}};
+
+ // Should be the same if we're serializing for explain or for internal use.
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(false));
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(true));
+}
+
+TEST(InclusionProjection, ShouldOptimizeNestedExpressions) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.b" << BSON("$add" << BSON_ARRAY(1 << 2))));
+
+ inclusion.optimize();
+
+ auto expectedSerialization =
+ Document{{"_id", true}, {"a", Document{{"b", Document{{"$const", 3}}}}}};
+
+ // Should be the same if we're serializing for explain or for internal use.
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(false));
+ ASSERT_EQ(expectedSerialization, inclusion.serialize(true));
+}
+
+//
+// Top-level only.
+//
+
+TEST(InclusionProjectionExecutionTest, ShouldIncludeTopLevelField) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true));
+
+ // More than one field in document.
+ auto result = inclusion.applyProjection(Document{{"a", 1}, {"b", 2}});
+ auto expectedResult = Document{{"a", 1}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Specified field is the only field in the document.
+ result = inclusion.applyProjection(Document{{"a", 1}});
+ expectedResult = Document{{"a", 1}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Specified field is not present in the document.
+ result = inclusion.applyProjection(Document{{"c", 1}});
+ expectedResult = Document{};
+ ASSERT_EQ(result, expectedResult);
+
+ // There are no fields in the document.
+ result = inclusion.applyProjection(Document{});
+ expectedResult = Document{};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldAddComputedTopLevelField) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("newField" << wrapInLiteral("computedVal")));
+ auto result = inclusion.applyProjection(Document{});
+ auto expectedResult = Document{{"newField", "computedVal"}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Computed field should replace existing field.
+ result = inclusion.applyProjection(Document{{"newField", "preExisting"}});
+ expectedResult = Document{{"newField", "computedVal"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyBothInclusionsAndComputedFields) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true << "newField" << wrapInLiteral("computedVal")));
+ auto result = inclusion.applyProjection(Document{{"a", 1}});
+ auto expectedResult = Document{{"a", 1}, {"newField", "computedVal"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldIncludeFieldsInOrderOfInputDoc) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("first" << true << "second" << true << "third" << true));
+ auto inputDoc = Document{{"second", 1}, {"first", 0}, {"third", 2}};
+ auto result = inclusion.applyProjection(inputDoc);
+ ASSERT_EQ(result, inputDoc);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyComputedFieldsInOrderSpecified) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("firstComputed" << wrapInLiteral("FIRST") << "secondComputed"
+ << wrapInLiteral("SECOND")));
+ auto result = inclusion.applyProjection(Document{{"first", 0}, {"second", 1}, {"third", 2}});
+ auto expectedResult = Document{{"firstComputed", "FIRST"}, {"secondComputed", "SECOND"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldImplicitlyIncludeId) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true));
+ auto result = inclusion.applyProjection(Document{{"_id", "ID"}, {"a", 1}, {"b", 2}});
+ auto expectedResult = Document{{"_id", "ID"}, {"a", 1}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Should leave the "_id" in the same place as in the original document.
+ result = inclusion.applyProjection(Document{{"a", 1}, {"b", 2}, {"_id", "ID"}});
+ expectedResult = Document{{"a", 1}, {"_id", "ID"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldImplicitlyIncludeIdWithComputedFields) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("newField" << wrapInLiteral("computedVal")));
+ auto result = inclusion.applyProjection(Document{{"_id", "ID"}, {"a", 1}});
+ auto expectedResult = Document{{"_id", "ID"}, {"newField", "computedVal"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldIncludeIdIfExplicitlyIncluded) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true << "_id" << true << "b" << true));
+ auto result = inclusion.applyProjection(Document{{"_id", "ID"}, {"a", 1}, {"b", 2}, {"c", 3}});
+ auto expectedResult = Document{{"_id", "ID"}, {"a", 1}, {"b", 2}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldExcludeIdIfExplicitlyExcluded) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << true << "_id" << false));
+ auto result = inclusion.applyProjection(Document{{"a", 1}, {"b", 2}, {"_id", "ID"}});
+ auto expectedResult = Document{{"a", 1}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldReplaceIdWithComputedId) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("_id" << wrapInLiteral("newId")));
+ auto result = inclusion.applyProjection(Document{{"a", 1}, {"b", 2}, {"_id", "ID"}});
+ auto expectedResult = Document{{"_id", "newId"}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+//
+// Projections with nested fields.
+//
+
+TEST(InclusionProjectionExecutionTest, ShouldIncludeSimpleDottedFieldFromSubDoc) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.b" << true));
+
+ // More than one field in sub document.
+ auto result = inclusion.applyProjection(Document{{"a", Document{{"b", 1}, {"c", 2}}}});
+ auto expectedResult = Document{{"a", Document{{"b", 1}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Specified field is the only field in the sub document.
+ result = inclusion.applyProjection(Document{{"a", Document{{"b", 1}}}});
+ expectedResult = Document{{"a", Document{{"b", 1}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Specified field is not present in the sub document.
+ result = inclusion.applyProjection(Document{{"a", Document{{"c", 1}}}});
+ expectedResult = Document{{"a", Document{}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // There are no fields in sub document.
+ result = inclusion.applyProjection(Document{{"a", Document{}}});
+ expectedResult = Document{{"a", Document{}}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldNotCreateSubDocIfDottedIncludedFieldDoesNotExist) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("sub.target" << true));
+
+ // Should not add the path if it doesn't exist.
+ auto result = inclusion.applyProjection(Document{});
+ auto expectedResult = Document{};
+ ASSERT_EQ(result, expectedResult);
+
+ // Should not replace the first part of the path if that part exists.
+ result = inclusion.applyProjection(Document{{"sub", "notADocument"}});
+ expectedResult = Document{};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyDottedInclusionToEachElementInArray) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.b" << true));
+
+ vector<Value> nestedValues = {Value(1),
+ Value(Document{}),
+ Value(Document{{"b", 1}}),
+ Value(Document{{"b", 1}, {"c", 2}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(1), Value(Document{{"c", 1}})})};
+
+ // Drops non-documents and non-arrays. Applies projection to documents, recurses on nested
+ // arrays.
+ vector<Value> expectedNestedValues = {Value(),
+ Value(Document{}),
+ Value(Document{{"b", 1}}),
+ Value(Document{{"b", 1}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(), Value(Document{})})};
+ auto result = inclusion.applyProjection(Document{{"a", nestedValues}});
+ auto expectedResult = Document{{"a", expectedNestedValues}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldAddComputedDottedFieldToSubDocument) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("sub.target" << wrapInLiteral("computedVal")));
+
+ // Other fields exist in sub document, one of which is the specified field.
+ auto result = inclusion.applyProjection(Document{{"sub", Document{{"target", 1}, {"c", 2}}}});
+ auto expectedResult = Document{{"sub", Document{{"target", "computedVal"}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Specified field is not present in the sub document.
+ result = inclusion.applyProjection(Document{{"sub", Document{{"c", 1}}}});
+ expectedResult = Document{{"sub", Document{{"target", "computedVal"}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // There are no fields in sub document.
+ result = inclusion.applyProjection(Document{{"sub", Document{}}});
+ expectedResult = Document{{"sub", Document{{"target", "computedVal"}}}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldCreateSubDocIfDottedComputedFieldDoesntExist) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("sub.target" << wrapInLiteral("computedVal")));
+
+ // Should add the path if it doesn't exist.
+ auto result = inclusion.applyProjection(Document{});
+ auto expectedResult = Document{{"sub", Document{{"target", "computedVal"}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Should replace non-documents with documents.
+ result = inclusion.applyProjection(Document{{"sub", "notADocument"}});
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldCreateNestedSubDocumentsAllTheWayToComputedField) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.b.c.d" << wrapInLiteral("computedVal")));
+
+ // Should add the path if it doesn't exist.
+ auto result = inclusion.applyProjection(Document{});
+ auto expectedResult =
+ Document{{"a", Document{{"b", Document{{"c", Document{{"d", "computedVal"}}}}}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ // Should replace non-documents with documents.
+ result = inclusion.applyProjection(Document{{"a", Document{{"b", "other"}}}});
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldAddComputedDottedFieldToEachElementInArray) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.b" << wrapInLiteral("COMPUTED")));
+
+ vector<Value> nestedValues = {Value(1),
+ Value(Document{}),
+ Value(Document{{"b", 1}}),
+ Value(Document{{"b", 1}, {"c", 2}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(1), Value(Document{{"c", 1}})})};
+ vector<Value> expectedNestedValues = {Value(Document{{"b", "COMPUTED"}}),
+ Value(Document{{"b", "COMPUTED"}}),
+ Value(Document{{"b", "COMPUTED"}}),
+ Value(Document{{"b", "COMPUTED"}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(Document{{"b", "COMPUTED"}}),
+ Value(Document{{"b", "COMPUTED"}})})};
+ auto result = inclusion.applyProjection(Document{{"a", nestedValues}});
+ auto expectedResult = Document{{"a", expectedNestedValues}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyInclusionsAndAdditionsToEachElementInArray) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a.inc" << true << "a.comp" << wrapInLiteral("COMPUTED")));
+
+ vector<Value> nestedValues = {Value(1),
+ Value(Document{}),
+ Value(Document{{"inc", 1}}),
+ Value(Document{{"inc", 1}, {"c", 2}}),
+ Value(Document{{"c", 2}, {"inc", 1}}),
+ Value(Document{{"inc", 1}, {"c", 2}, {"comp", "original"}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(1), Value(Document{{"inc", 1}})})};
+ vector<Value> expectedNestedValues = {
+ Value(Document{{"comp", "COMPUTED"}}),
+ Value(Document{{"comp", "COMPUTED"}}),
+ Value(Document{{"inc", 1}, {"comp", "COMPUTED"}}),
+ Value(Document{{"inc", 1}, {"comp", "COMPUTED"}}),
+ Value(Document{{"inc", 1}, {"comp", "COMPUTED"}}),
+ Value(Document{{"inc", 1}, {"comp", "COMPUTED"}}),
+ Value(vector<Value>{}),
+ Value(vector<Value>{Value(Document{{"comp", "COMPUTED"}}),
+ Value(Document{{"inc", 1}, {"comp", "COMPUTED"}})})};
+ auto result = inclusion.applyProjection(Document{{"a", nestedValues}});
+ auto expectedResult = Document{{"a", expectedNestedValues}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldAddOrIncludeSubFieldsOfId) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("_id.X" << true << "_id.Z" << wrapInLiteral("NEW")));
+ auto result = inclusion.applyProjection(Document{{"_id", Document{{"X", 1}, {"Y", 2}}}});
+ auto expectedResult = Document{{"_id", Document{{"X", 1}, {"Z", "NEW"}}}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldAllowMixedNestedAndDottedFields) {
+ ParsedInclusionProjection inclusion;
+ // Include all of "a.b", "a.c", "a.d", and "a.e".
+ // Add new computed fields "a.W", "a.X", "a.Y", and "a.Z".
+ inclusion.parse(BSON(
+ "a.b" << true << "a.c" << true << "a.W" << wrapInLiteral("W") << "a.X" << wrapInLiteral("X")
+ << "a"
+ << BSON("d" << true << "e" << true << "Y" << wrapInLiteral("Y") << "Z"
+ << wrapInLiteral("Z"))));
+ auto result = inclusion.applyProjection(
+ Document{{"a", Document{{"b", "b"}, {"c", "c"}, {"d", "d"}, {"e", "e"}, {"f", "f"}}}});
+ auto expectedResult = Document{{"a",
+ Document{{"b", "b"},
+ {"c", "c"},
+ {"d", "d"},
+ {"e", "e"},
+ {"W", "W"},
+ {"X", "X"},
+ {"Y", "Y"},
+ {"Z", "Z"}}}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyNestedComputedFieldsInOrderSpecified) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("a" << wrapInLiteral("FIRST") << "b.c" << wrapInLiteral("SECOND")));
+ auto result = inclusion.applyProjection(Document{});
+ auto expectedResult = Document{{"a", "FIRST"}, {"b", Document{{"c", "SECOND"}}}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ShouldApplyComputedFieldsAfterAllInclusions) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("b.c" << wrapInLiteral("NEW") << "a" << true));
+ auto result = inclusion.applyProjection(Document{{"a", 1}});
+ auto expectedResult = Document{{"a", 1}, {"b", Document{{"c", "NEW"}}}};
+ ASSERT_EQ(result, expectedResult);
+
+ result = inclusion.applyProjection(Document{{"a", 1}, {"b", 4}});
+ ASSERT_EQ(result, expectedResult);
+
+ // In this case, the field 'b' shows up first and has a nested inclusion or computed field. Even
+ // though it is a computed field, it will appear first in the output document. This is
+ // inconsistent, but the expected behavior, and a consequence of applying the projection
+ // recursively to each sub-document.
+ result = inclusion.applyProjection(Document{{"b", 4}, {"a", 1}});
+ expectedResult = Document{{"b", Document{{"c", "NEW"}}}, {"a", 1}};
+ ASSERT_EQ(result, expectedResult);
+}
+
+TEST(InclusionProjectionExecutionTest, ComputedFieldReplacingExistingShouldAppearAfterInclusions) {
+ ParsedInclusionProjection inclusion;
+ inclusion.parse(BSON("b" << wrapInLiteral("NEW") << "a" << true));
+ auto result = inclusion.applyProjection(Document{{"b", 1}, {"a", 1}});
+ auto expectedResult = Document{{"a", 1}, {"b", "NEW"}};
+ ASSERT_EQ(result, expectedResult);
+
+ result = inclusion.applyProjection(Document{{"a", 1}, {"b", 4}});
+ ASSERT_EQ(result, expectedResult);
+}
+
+//
+// Misc.
+//
+
+TEST(InclusionProjectionExecutionTest, ShouldAlwaysKeepMetadataFromOriginalDoc) {
+ ParsedInclusionProjection inclusion;
+ 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_EQ(result, expectedDoc.freeze());
+}
+
+} // namespace
+} // namespace parsed_aggregation_projection
+} // namespace mongo