/** * 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 . * * 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_add_fields.h" #include #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/document_value_test_util.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/pipeline/value.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace parsed_aggregation_projection { namespace { using std::vector; // These ParsedAddFields spec tests are a subset of the ParsedAggregationProjection creation tests. // ParsedAddFields should behave the same way, but does not use the same creation, so we include // an abbreviation of the same tests here. // Verify that ParsedAddFields rejects specifications with conflicting field paths. TEST(ParsedAddFieldsSpec, ThrowsOnCreationWithConflictingFieldPaths) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); // These specs contain the same exact path. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << 1 << "a" << 2)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("b" << 1 << "b" << 2))), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("_id" << 3 << "_id" << true)), UserException); // These specs contain overlapping paths. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << 1 << "a.b" << 2)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a.b.c" << 1 << "a" << 2)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("_id" << true << "_id.x" << true)), UserException); } // Verify that ParsedAddFields rejects specifications that contain invalid field paths. TEST(ParsedAddFieldsSpec, ThrowsOnCreationWithInvalidFieldPath) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); // Dotted subfields are not allowed. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("b.c" << true))), UserException); // The user cannot start a field with $. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("$dollar" << 0)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("c.$d" << true)), UserException); // Empty field names should throw an error. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("" << 2)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("" << true))), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("" << BSON("a" << true))), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a." << true)), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON(".a" << true)), UserException); } // Verify that ParsedAddFields rejects specifications that contain empty objects or invalid // expressions. TEST(ParsedAddFieldsSpec, ThrowsOnCreationWithInvalidObjectsOrExpressions) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); // Invalid expressions should be rejected. ASSERT_THROWS(ParsedAddFields::create( expCtx, BSON("a" << BSON("$add" << BSON_ARRAY(4 << 2) << "b" << 1))), UserException); ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("$gt" << BSON("bad" << "arguments")))), UserException); ASSERT_THROWS(ParsedAddFields::create( expCtx, BSON("a" << false << "b" << BSON("$unknown" << BSON_ARRAY(4 << 2)))), UserException); // Empty specifications are not allowed. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSONObj()), UserException); // Empty nested objects are not allowed. ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSONObj())), UserException); } TEST(ParsedAddFields, DoesNotErrorOnTwoNestedFields) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields::create(expCtx, BSON("a.b" << true << "a.c" << true)); ParsedAddFields::create(expCtx, BSON("a.b" << true << "a" << BSON("c" << true))); } // Verify that replaced fields are not included as dependencies. TEST(ParsedAddFieldsDeps, RemovesReplaceFieldsFromDependencies) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a" << true)); DepsTracker deps; addition.addDependencies(&deps); ASSERT_EQ(deps.fields.size(), 0UL); ASSERT_EQ(deps.fields.count("_id"), 0UL); // Not explicitly included. ASSERT_EQ(deps.fields.count("a"), 0UL); // Set to true. } // Verify that adding nested fields keeps the top-level field as a dependency. TEST(ParsedAddFieldsDeps, IncludesTopLevelFieldInDependenciesWhenAddingNestedFields) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("x.y" << true)); DepsTracker deps; addition.addDependencies(&deps); ASSERT_EQ(deps.fields.size(), 1UL); ASSERT_EQ(deps.fields.count("_id"), 0UL); // Not explicitly included. ASSERT_EQ(deps.fields.count("x.y"), 0UL); // Set to true. ASSERT_EQ(deps.fields.count("x"), 1UL); // Top-level of nested field included. } // Verify that fields that an expression depends on are added to the dependencies. TEST(ParsedAddFieldsDeps, AddsDependenciesForComputedFields) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("x.y" << "$z" << "a" << "$b")); DepsTracker deps; addition.addDependencies(&deps); ASSERT_EQ(deps.fields.size(), 3UL); ASSERT_EQ(deps.fields.count("_id"), 0UL); // Not explicitly included. ASSERT_EQ(deps.fields.count("z"), 1UL); // Needed by the ExpressionFieldPath for x.y. ASSERT_EQ(deps.fields.count("x"), 1UL); // Preserves top-level field, for structure. ASSERT_EQ(deps.fields.count("a"), 0UL); // Replaced, so omitted. ASSERT_EQ(deps.fields.count("b"), 1UL); // Needed by the ExpressionFieldPath for a. } // Verify that the serialization produces the correct output: converting numbers and literals to // their corresponding $const form. TEST(ParsedAddFieldsSerialize, SerializesToCorrectForm) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(fromjson("{a: {$add: ['$a', 2]}, b: {d: 3}, 'x.y': {$literal: 4}}")); auto expectedSerialization = Document( fromjson("{a: {$add: [\"$a\", {$const: 2}]}, b: {d: {$const: 3}}, x: {y: {$const: 4}}}")); // Should be the same if we're serializing for explain or for internal use. ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kQueryPlanner)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecStats)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecAllPlans)); } // Verify that serialize treats the _id field as any other field: including when explicity included. TEST(ParsedAddFieldsSerialize, AddsIdToSerializeWhenExplicitlyIncluded) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("_id" << false)); // Adds explicit "_id" setting field, serializes expressions. auto expectedSerialization = Document(fromjson("{_id: {$const: false}}")); // Should be the same if we're serializing for explain or for internal use. ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kQueryPlanner)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecStats)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecAllPlans)); } // Verify that serialize treats the _id field as any other field: excluded when not explicitly // listed in the specification. We add this check because it is different behavior from $project, // yet they derive from the same parent class. If the parent class were to change, this test would // fail. TEST(ParsedAddFieldsSerialize, OmitsIdFromSerializeWhenNotIncluded) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a" << true)); // Does not implicitly include "_id" field. auto expectedSerialization = Document(fromjson("{a: {$const: true}}")); // Should be the same if we're serializing for explain or for internal use. ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kQueryPlanner)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecStats)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecAllPlans)); } // Verify that the $addFields stage optimizes expressions into simpler forms when possible. TEST(ParsedAddFieldsOptimize, OptimizesTopLevelExpressions) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a" << BSON("$add" << BSON_ARRAY(1 << 2)))); addition.optimize(); auto expectedSerialization = Document{{"a", Document{{"$const", 3}}}}; // Should be the same if we're serializing for explain or for internal use. ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kQueryPlanner)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecStats)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecAllPlans)); } // Verify that the $addFields stage optimizes expressions even when they are nested. TEST(ParsedAddFieldsOptimize, ShouldOptimizeNestedExpressions) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a.b" << BSON("$add" << BSON_ARRAY(1 << 2)))); addition.optimize(); auto expectedSerialization = Document{{"a", Document{{"b", Document{{"$const", 3}}}}}}; // Should be the same if we're serializing for explain or for internal use. ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(boost::none)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kQueryPlanner)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecStats)); ASSERT_DOCUMENT_EQ(expectedSerialization, addition.serialize(ExplainOptions::Verbosity::kExecAllPlans)); } // // Top-level only. // // Verify that a new field is added to the end of the document. TEST(ParsedAddFieldsExecutionTest, AddsNewFieldToEndOfDocument) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("c" << 3)); // There are no fields in the document. auto result = addition.applyProjection(Document{}); auto expectedResult = Document{{"c", 3}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // There are fields in the document but none of them are the added field. result = addition.applyProjection(Document{{"a", 1}, {"b", 2}}); expectedResult = Document{{"a", 1}, {"b", 2}, {"c", 3}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that an existing field is replaced and stays in the same order in the document. TEST(ParsedAddFieldsExecutionTest, ReplacesFieldThatAlreadyExistsInDocument) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("c" << 3)); // Specified field is the only field in the document, and is replaced. auto result = addition.applyProjection(Document{{"c", 1}}); auto expectedResult = Document{{"c", 3}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // Specified field is one of the fields in the document, and is replaced in its existing order. result = addition.applyProjection(Document{{"c", 1}, {"b", 2}}); expectedResult = Document{{"c", 3}, {"b", 2}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that replacing multiple fields preserves the original field order in the document. TEST(ParsedAddFieldsExecutionTest, ReplacesMultipleFieldsWhilePreservingInputFieldOrder) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("second" << "SECOND" << "first" << "FIRST")); auto result = addition.applyProjection(Document{{"first", 0}, {"second", 1}, {"third", 2}}); auto expectedResult = Document{{"first", "FIRST"_sd}, {"second", "SECOND"_sd}, {"third", 2}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that adding multiple fields adds the fields in the order specified. TEST(ParsedAddFieldsExecutionTest, AddsNewFieldsAfterExistingFieldsInOrderSpecified) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("firstComputed" << "FIRST" << "secondComputed" << "SECOND")); auto result = addition.applyProjection(Document{{"first", 0}, {"second", 1}, {"third", 2}}); auto expectedResult = Document{{"first", 0}, {"second", 1}, {"third", 2}, {"firstComputed", "FIRST"_sd}, {"secondComputed", "SECOND"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that both adding and replacing fields at the same time follows the same rules as doing // each independently. TEST(ParsedAddFieldsExecutionTest, ReplacesAndAddsNewFieldsWithSameOrderingRulesAsSeparately) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("firstComputed" << "FIRST" << "second" << "SECOND")); auto result = addition.applyProjection(Document{{"first", 0}, {"second", 1}, {"third", 2}}); auto expectedResult = Document{ {"first", 0}, {"second", "SECOND"_sd}, {"third", 2}, {"firstComputed", "FIRST"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that _id is included just like a regular field, in whatever order it appears in the // input document, when adding new fields. TEST(ParsedAddFieldsExecutionTest, IdFieldIsKeptInOrderItAppearsInInputDocument) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("newField" << "computedVal")); auto result = addition.applyProjection(Document{{"_id", "ID"_sd}, {"a", 1}}); auto expectedResult = Document{{"_id", "ID"_sd}, {"a", 1}, {"newField", "computedVal"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); result = addition.applyProjection(Document{{"a", 1}, {"_id", "ID"_sd}}); expectedResult = Document{{"a", 1}, {"_id", "ID"_sd}, {"newField", "computedVal"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that replacing or adding _id works just like any other field. TEST(ParsedAddFieldsExecutionTest, ShouldReplaceIdWithComputedId) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("_id" << "newId")); auto result = addition.applyProjection(Document{{"_id", "ID"_sd}, {"a", 1}}); auto expectedResult = Document{{"_id", "newId"_sd}, {"a", 1}}; ASSERT_DOCUMENT_EQ(result, expectedResult); result = addition.applyProjection(Document{{"a", 1}, {"_id", "ID"_sd}}); expectedResult = Document{{"a", 1}, {"_id", "newId"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); result = addition.applyProjection(Document{{"a", 1}}); expectedResult = Document{{"a", 1}, {"_id", "newId"_sd}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // // Adding nested fields. // // Verify that adding a dotted field keeps the other fields in the subdocument. TEST(ParsedAddFieldsExecutionTest, KeepsExistingSubFieldsWhenAddingSimpleDottedFieldToSubDoc) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a.b" << true)); // More than one field in sub document. auto result = addition.applyProjection(Document{{"a", Document{{"b", 1}, {"c", 2}}}}); auto expectedResult = Document{{"a", Document{{"b", true}, {"c", 2}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // Specified field is the only field in the sub document. result = addition.applyProjection(Document{{"a", Document{{"b", 1}}}}); expectedResult = Document{{"a", Document{{"b", true}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // Specified field is not present in the sub document. result = addition.applyProjection(Document{{"a", Document{{"c", 1}}}}); expectedResult = Document{{"a", Document{{"c", 1}, {"b", true}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // There are no fields in sub document. result = addition.applyProjection(Document{{"a", Document{}}}); expectedResult = Document{{"a", Document{{"b", true}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that creating a dotted field creates the subdocument structure necessary. TEST(ParsedAddFieldsExecutionTest, CreatesSubDocIfDottedAddedFieldDoesNotExist) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("sub.target" << true)); // Should add the path if it doesn't exist. auto result = addition.applyProjection(Document{}); auto expectedResult = Document{{"sub", Document{{"target", true}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // Should replace the second part of the path if that part already exists. result = addition.applyProjection(Document{{"sub", "notADocument"_sd}}); expectedResult = Document{{"sub", Document{{"target", true}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that adding a dotted value to an array field sets the field in every element of the array. // SERVER-25200: make this agree with $set. TEST(ParsedAddFieldsExecutionTest, AppliesDottedAdditionToEachElementInArray) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a.b" << true)); vector nestedValues = {Value(1), Value(Document{}), Value(Document{{"b", 1}}), Value(Document{{"b", 1}, {"c", 2}}), Value(vector{}), Value(vector{Value(1), Value(Document{{"c", 1}})})}; // Adds the field "b" to every object in the array. Recurses on non-empty nested arrays. vector expectedNestedValues = { Value(Document{{"b", true}}), Value(Document{{"b", true}}), Value(Document{{"b", true}}), Value(Document{{"b", true}, {"c", 2}}), Value(vector{}), Value(vector{Value(Document{{"b", true}}), Value(Document{{"c", 1}, {"b", true}})})}; auto result = addition.applyProjection(Document{{"a", nestedValues}}); auto expectedResult = Document{{"a", expectedNestedValues}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that creation of the subdocument structure works for many layers of nesting. TEST(ParsedAddFieldsExecutionTest, CreatesNestedSubDocumentsAllTheWayToAddedField) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a.b.c.d" << "computedVal")); // Should add the path if it doesn't exist. auto result = addition.applyProjection(Document{}); auto expectedResult = Document{{"a", Document{{"b", Document{{"c", Document{{"d", "computedVal"_sd}}}}}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); // Should replace non-documents with documents. result = addition.applyProjection(Document{{"a", Document{{"b", "other"_sd}}}}); ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that _id is not special: we can add subfields to it as well. TEST(ParsedAddFieldsExecutionTest, AddsSubFieldsOfId) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("_id.X" << true << "_id.Z" << "NEW")); auto result = addition.applyProjection(Document{{"_id", Document{{"X", 1}, {"Y", 2}}}}); auto expectedResult = Document{{"_id", Document{{"X", true}, {"Y", 2}, {"Z", "NEW"_sd}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that both ways of specifying nested fields -- both dotted notation and nesting -- // can be used together in the same specification. TEST(ParsedAddFieldsExecutionTest, ShouldAllowMixedNestedAndDottedFields) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); // 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". addition.parse(BSON("a.b" << true << "a.c" << true << "a.W" << "W" << "a.X" << "X" << "a" << BSON("d" << true << "e" << true << "Y" << "Y" << "Z" << "Z"))); auto result = addition.applyProjection(Document{ {"a", Document{{"b", "b"_sd}, {"c", "c"_sd}, {"d", "d"_sd}, {"e", "e"_sd}, {"f", "f"_sd}}}}); auto expectedResult = Document{{"a", Document{{"b", true}, {"c", true}, {"d", true}, {"e", true}, {"f", "f"_sd}, {"W", "W"_sd}, {"X", "X"_sd}, {"Y", "Y"_sd}, {"Z", "Z"_sd}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // Verify that adding nested fields preserves the addition order in the spec. TEST(ParsedAddFieldsExecutionTest, AddsNestedAddedFieldsInOrderSpecified) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("b.d" << "FIRST" << "b.c" << "SECOND")); auto result = addition.applyProjection(Document{}); auto expectedResult = Document{{"b", Document{{"d", "FIRST"_sd}, {"c", "SECOND"_sd}}}}; ASSERT_DOCUMENT_EQ(result, expectedResult); } // // Misc/Metadata. // // Verify that the metadata is kept from the original input document. TEST(ParsedAddFieldsExecutionTest, AlwaysKeepsMetadataFromOriginalDoc) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); ParsedAddFields addition(expCtx); addition.parse(BSON("a" << true)); MutableDocument inputDocBuilder(Document{{"a", 1}}); inputDocBuilder.setRandMetaField(1.0); inputDocBuilder.setTextScore(10.0); Document inputDoc = inputDocBuilder.freeze(); auto result = addition.applyProjection(inputDoc); MutableDocument expectedDoc(Document{{"a", true}}); expectedDoc.copyMetaDataFrom(inputDoc); ASSERT_DOCUMENT_EQ(result, expectedDoc.freeze()); } } // namespace } // namespace parsed_aggregation_projection } // namespace mongo