summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec/projection_executor_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/exec/projection_executor_test.cpp')
-rw-r--r--src/mongo/db/exec/projection_executor_test.cpp682
1 files changed, 526 insertions, 156 deletions
diff --git a/src/mongo/db/exec/projection_executor_test.cpp b/src/mongo/db/exec/projection_executor_test.cpp
index 0d97b093b1b..6d0d06a84d4 100644
--- a/src/mongo/db/exec/projection_executor_test.cpp
+++ b/src/mongo/db/exec/projection_executor_test.cpp
@@ -29,212 +29,582 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/exec/projection_executor.h"
-#include "mongo/db/pipeline/aggregation_context_fixture.h"
-#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/query/projection_ast_util.h"
+
+#include <vector>
+
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/exec/document_value/document.h"
+#include "mongo/db/exec/document_value/value.h"
+#include "mongo/db/exec/inclusion_projection_executor.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/projection_parser.h"
#include "mongo/unittest/unittest.h"
namespace mongo::projection_executor {
namespace {
-constexpr auto kOptimzeExecutor = true;
+template <typename T>
+BSONObj wrapInLiteral(const T& arg) {
+ return BSON("$literal" << arg);
+}
-class ProjectionExecutorTest : public AggregationContextFixture {
-public:
- projection_ast::Projection parseWithDefaultPolicies(
- const BSONObj& projectionBson, boost::optional<BSONObj> matchExprBson = boost::none) {
- return parseWithPolicies(projectionBson, matchExprBson, ProjectionPolicies{});
- }
+// Helper to simplify the creation of a ProjectionExecutor with default policies.
+auto makeProjectionWithDefaultPolicies(BSONObj spec) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ProjectionPolicies defaultPolicies;
+ auto projection = projection_ast::parse(expCtx, spec, defaultPolicies);
+ return buildProjectionExecutor(
+ expCtx, &projection, defaultPolicies, true /* optimizeExecutor */);
+}
- projection_ast::Projection parseWithFindFeaturesEnabled(
- const BSONObj& projectionBson, boost::optional<BSONObj> matchExprBson = boost::none) {
- auto policy = ProjectionPolicies::findProjectionPolicies();
- return parseWithPolicies(projectionBson, matchExprBson, policy);
- }
+// Helper to simplify the creation of a ProjectionExecutor which bans computed fields.
+auto makeProjectionWithBannedComputedFields(BSONObj spec) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ProjectionPolicies banComputedFields{
+ ProjectionPolicies::kDefaultIdPolicyDefault,
+ ProjectionPolicies::kArrayRecursionPolicyDefault,
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields};
+ auto projection = projection_ast::parse(expCtx, spec, banComputedFields);
+ return buildProjectionExecutor(
+ expCtx, &projection, banComputedFields, true /* optimizeExecutor */);
+}
- projection_ast::Projection parseWithPolicies(const BSONObj& projectionBson,
- boost::optional<BSONObj> matchExprBson,
- ProjectionPolicies policies) {
- StatusWith<std::unique_ptr<MatchExpression>> swMatchExpression(nullptr);
- if (matchExprBson) {
- swMatchExpression = MatchExpressionParser::parse(*matchExprBson, getExpCtx());
- uassertStatusOK(swMatchExpression.getStatus());
- }
-
- return projection_ast::parse(getExpCtx(),
- projectionBson,
- swMatchExpression.getValue().get(),
- matchExprBson.get_value_or(BSONObj()),
- policies);
- }
-};
+//
+// Error cases.
+//
+
+TEST(ProjectionExecutorErrors, ShouldRejectDuplicateFieldNames) {
+ // Include/exclude the same field twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "a" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "a" << false)),
+ AssertionException);
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a" << BSON("b" << false << "b" << false))),
+ AssertionException);
+
+ // Mix of include/exclude and adding a field.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1) << "a" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "a" << wrapInLiteral(0))),
+ AssertionException);
+
+ // Adding the same field twice.
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1) << "a" << wrapInLiteral(0))),
+ AssertionException);
+}
+
+TEST(ProjectionExecutorErrors, ShouldRejectDuplicateIds) {
+ // Include/exclude _id twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id" << true << "_id" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id" << false << "_id" << false)),
+ AssertionException);
+
+ // Mix of including/excluding and adding _id.
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("_id" << wrapInLiteral(1) << "_id" << true)),
+ AssertionException);
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("_id" << false << "_id" << wrapInLiteral(0))),
+ AssertionException);
+
+ // Adding _id twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("_id" << wrapInLiteral(1) << "_id" << wrapInLiteral(0))),
+ AssertionException);
+}
+
+TEST(ProjectionExecutorErrors, ShouldRejectFieldsWithSharedPrefix) {
+ // Include/exclude Fields with a shared prefix.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "a.b" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a.b" << false << "a" << false)),
+ AssertionException);
+
+ // Mix of include/exclude and adding a shared prefix.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1) << "a.b" << true)),
+ AssertionException);
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a.b" << false << "a" << wrapInLiteral(0))),
+ AssertionException);
+
+ // Adding a shared prefix twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << wrapInLiteral(1) << "a.b" << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b.c.d" << wrapInLiteral(1) << "a.b.c" << wrapInLiteral(0))),
+ AssertionException);
+}
+
+TEST(ProjectionExecutorErrors, ShouldRejectPathConflictsWithNonAlphaNumericCharacters) {
+ // 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(
+ BSON("a.b-c" << true << "a.b" << true << "a.b?c" << true << "a.b c" << true)));
+ ASSERT(makeProjectionWithDefaultPolicies(
+ BSON("a.b c" << false << "a.b?c" << false << "a.b" << false << "a.b-c" << false)));
+
+ // Then assert that we throw when we introduce a prefixed field.
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a.b-c" << true << "a.b" << true << "a.b?c" << true
+ << "a.b c" << true << "a.b.d" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a.b.d" << false << "a.b c" << false
+ << "a.b?c" << false << "a.b"
+ << false << "a.b-c" << false)),
+ AssertionException);
+
+ // Adding the same field twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b?c" << wrapInLiteral(1) << "a.b?c" << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b c" << wrapInLiteral(0) << "a.b c" << wrapInLiteral(1))),
+ AssertionException);
+
+ // Mix of include/exclude and adding a shared prefix.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b-c" << true << "a.b" << wrapInLiteral(1) << "a.b?c" << true
+ << "a.b c" << true << "a.b.d" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b.d" << false << "a.b c" << false << "a.b?c" << false << "a.b"
+ << wrapInLiteral(0) << "a.b-c" << false)),
+ AssertionException);
-TEST_F(ProjectionExecutorTest, CanProjectInclusionWithIdPath) {
- auto projWithId = parseWithDefaultPolicies(fromjson("{a: 1, _id: 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &projWithId, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{_id: 123, a: 'abc'}")},
- executor->applyTransformation(
- Document{fromjson("{_id: 123, a: 'abc', b: 'def', c: 'ghi'}")}));
+ // Adding a shared prefix twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b-c" << wrapInLiteral(1) << "a.b" << wrapInLiteral(1) << "a.b?c"
+ << wrapInLiteral(1) << "a.b c" << wrapInLiteral(1) << "a.b.d"
+ << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a.b.d" << wrapInLiteral(1) << "a.b c" << wrapInLiteral(1) << "a.b?c"
+ << wrapInLiteral(1) << "a.b" << wrapInLiteral(0) << "a.b-c"
+ << wrapInLiteral(1))),
+ AssertionException);
+}
+
+TEST(ProjectionExecutorErrors, ShouldRejectMixOfIdAndSubFieldsOfId) {
+ // Include/exclude _id twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id" << true << "_id.x" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id.x" << false << "_id" << false)),
+ AssertionException);
- auto projWithoutId = parseWithDefaultPolicies(fromjson("{a: 1, _id: 0}"));
- executor = buildProjectionExecutor(getExpCtx(), &projWithoutId, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 'abc'}")},
- executor->applyTransformation(
- Document{fromjson("{_id: 123, a: 'abc', b: 'def', c: 'ghi'}")}));
+ // Mix of including/excluding and adding _id.
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("_id" << wrapInLiteral(1) << "_id.x" << true)),
+ AssertionException);
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("_id.x" << false << "_id" << wrapInLiteral(0))),
+ AssertionException);
+
+ // Adding _id twice.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("_id" << wrapInLiteral(1) << "_id.x" << wrapInLiteral(0))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("_id.b.c.d" << wrapInLiteral(1) << "_id.b.c" << wrapInLiteral(0))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectInclusionUndottedPath) {
- auto proj = parseWithDefaultPolicies(fromjson("{a: 1, b: 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: 'abc', b: 'def'}")},
- executor->applyTransformation(Document{fromjson("{a: 'abc', b: 'def', c: 'ghi'}")}));
+TEST(ProjectionExecutorErrors, ShouldAllowMixOfIdInclusionAndExclusion) {
+ // Mixing "_id" inclusion with exclusion.
+ auto parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << true << "a" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << false << "_id" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << true << "a.b.c" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
}
-TEST_F(ProjectionExecutorTest, CanProjectInclusionDottedPath) {
- auto proj = parseWithDefaultPolicies(fromjson("{'a.b': 1, 'a.d': 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: 'abc', d: 'ghi'}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: 'abc', c: 'def', d: 'ghi'}}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectMixOfInclusionAndExclusion) {
+ // Simple mix.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "b" << false)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "b" << true)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("b" << false << "c" << true))),
+ AssertionException);
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("_id" << BSON("b" << false << "c" << true))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id.b" << false << "a.c" << true)),
+ AssertionException);
+
+ // Mix while also adding a field.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << true << "b" << wrapInLiteral(1) << "c" << false)),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << false << "b" << wrapInLiteral(1) << "c" << true)),
+ AssertionException);
+
+ // Mix of "_id" subfield inclusion and exclusion.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("_id.x" << true << "a.b.c" << false)),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectExpression) {
- auto proj = parseWithDefaultPolicies(fromjson("{c: {$add: ['$a', '$b']}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{c: 3}")},
- executor->applyTransformation(Document{fromjson("{a: 1, b: 2}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectMixOfExclusionAndComputedFields) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "b" << wrapInLiteral(1))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1) << "b" << false)),
+ AssertionException);
+
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a.b" << false << "a.c" << wrapInLiteral(1))),
+ AssertionException);
+
+ ASSERT_THROWS(
+ makeProjectionWithDefaultPolicies(BSON("a.b" << wrapInLiteral(1) << "a.c" << false)),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << false << "c" << wrapInLiteral(1)))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << false))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectExclusionWithIdPath) {
- auto projWithoutId = parseWithDefaultPolicies(fromjson("{a: 0, _id: 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &projWithoutId, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{b: 'def', c: 'ghi'}")},
- executor->applyTransformation(
- Document{fromjson("{_id: 123, a: 'abc', b: 'def', c: 'ghi'}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectFieldNamesStartingWithADollar) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$dollar" << 0)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$dollar" << 1)), AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("b.$dollar" << 0)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("b.$dollar" << 1)), AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("b" << BSON("$dollar" << 0))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("b" << BSON("$dollar" << 1))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$add" << 0)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$add" << 1)), AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectExclusionUndottedPath) {
- auto proj = parseWithDefaultPolicies(fromjson("{a: 0, b: 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{c: 'ghi'}")},
- executor->applyTransformation(Document{fromjson("{a: 'abc', b: 'def', c: 'ghi'}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectTopLevelExpressions) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$add" << BSON_ARRAY(4 << 2))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectExclusionDottedPath) {
- auto proj = parseWithDefaultPolicies(fromjson("{'a.b': 0, 'a.d': 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {c: 'def'}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: 'abc', c: 'def', d: 'ghi'}}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectExpressionWithMultipleFieldNames) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("$add" << BSON_ARRAY(4 << 2) << "b" << 1))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << 1 << "$add" << BSON_ARRAY(4 << 2)))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << BSON("c" << 1 << "$add" << BSON_ARRAY(4 << 2))))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << BSON("$add" << BSON_ARRAY(4 << 2) << "c" << 1)))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindPositional) {
- auto proj =
- parseWithFindFeaturesEnabled(fromjson("{'a.b.$': 1}"), fromjson("{'a.b': {$gte: 3}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [3]}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3,4]}}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectEmptyProjection) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSONObj()), AssertionException);
+}
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [4]}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [4, 3, 2]}}")}));
+TEST(ProjectionExecutorErrors, ShouldRejectEmptyNestedObject) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSONObj())), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << false << "b" << BSONObj())),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << true << "b" << BSONObj())),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a.b" << BSONObj())), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("b" << BSONObj()))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindElemMatchWithInclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {b: {$gte: 3}}}, c: 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: [{b: 3}]}")},
- executor->applyTransformation(Document{fromjson("{a: [{b: 1}, {b: 2}, {b: 3}]}")}));
+TEST(ProjectionExecutorErrors, ShouldErrorOnInvalidExpression) {
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << false << "b" << BSON("$unknown" << BSON_ARRAY(4 << 2)))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(
+ BSON("a" << true << "b" << BSON("$unknown" << BSON_ARRAY(4 << 2)))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindElemMatch) {
+TEST(ProjectionExecutorErrors, ShouldErrorOnInvalidFieldPath) {
+ // Empty field names.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << wrapInLiteral(2))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << true)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << false)), AssertionException);
- const BSONObj obj = fromjson("{a: [{b: 3, c: 1}, {b: 1, c: 2}, {b: 1, c: 3}]}");
- {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {b: 1}}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 1, c: 2}]}")},
- executor->applyTransformation(Document{obj}));
- }
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("" << true))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("" << false))),
+ AssertionException);
- {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {b: 1, c: 3}}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 1, c: 3}]}")},
- executor->applyTransformation(Document{obj}));
- }
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << BSON("a" << true))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << BSON("a" << false))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a." << true)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a." << false)), AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON(".a" << true)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON(".a" << false)), AssertionException);
+
+ // Not testing field names with null bytes, since that is invalid BSON, and won't make it to the
+ // $project stage without a previous error.
+
+ // Field names starting with '$'.
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$x" << wrapInLiteral(2))),
+ AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("c.$d" << true)), AssertionException);
+ ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("c.$d" << false)), AssertionException);
+}
+
+TEST(ProjectionExecutorErrors, ShouldNotErrorOnTwoNestedFields) {
+ makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a.c" << true));
+ makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a" << BSON("c" << true)));
+}
+
+//
+// Determining exclusion vs. inclusion.
+//
+
+TEST(ProjectionExecutorType, ShouldAllowDottedFieldInSubDocument) {
+ auto proj = makeProjectionWithDefaultPolicies(BSON("a" << BSON("b.c" << true)));
+ ASSERT(proj->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ proj = makeProjectionWithDefaultPolicies(BSON("a" << BSON("b.c" << wrapInLiteral(1))));
+ ASSERT(proj->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ proj = makeProjectionWithDefaultPolicies(BSON("a" << BSON("b.c" << false)));
+ ASSERT(proj->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+}
+
+TEST(ProjectionExecutorType, ShouldDefaultToInclusionProjection) {
+ auto parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+}
+
+TEST(ProjectionExecutorType, ShouldDetectExclusionProjection) {
+ auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id.x" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << BSON("x" << false)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("x" << BSON("_id" << false)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+}
+
+TEST(ProjectionExecutorType, ShouldDetectInclusionProjection) {
+ auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << false << "a" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << false << "a.b.c" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id.x" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << BSON("x" << true)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("x" << BSON("_id" << true)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+}
+
+TEST(ProjectionExecutorType, ShouldTreatOnlyComputedFieldsAsAnInclusionProjection) {
+ auto parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject =
+ makeProjectionWithDefaultPolicies(BSON("_id" << false << "a" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject =
+ makeProjectionWithDefaultPolicies(BSON("_id" << false << "a.b.c" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id.x" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("_id" << BSON("x" << wrapInLiteral(1))));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("x" << BSON("_id" << wrapInLiteral(1))));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
}
-TEST_F(ProjectionExecutorTest, ElemMatchRespectsCollator) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- getExpCtx()->setCollator(&collator);
+TEST(ProjectionExecutorType, ShouldAllowMixOfInclusionAndComputedFields) {
+ auto parsedProject =
+ makeProjectionWithDefaultPolicies(BSON("a" << true << "b" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject =
+ makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a.c" << wrapInLiteral(1)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {$gte: 'abc'}}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
+ parsedProject = makeProjectionWithDefaultPolicies(
+ BSON("a" << BSON("b" << true << "c" << wrapInLiteral(1))));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{ a: [ \"zdd\" ] }")},
- executor->applyTransformation(Document{fromjson("{a: ['zaa', 'zbb', 'zdd', 'zee']}")}));
+ parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << BSON("b" << true << "c"
+ << "stringLiteral")));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindElemMatchWithExclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {b: {$gte: 3}}}, c: 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 3}], d: 'def'}")},
- executor->applyTransformation(Document{
- fromjson("{a: [{b: 1}, {b: 2}, {b: 3}], c: 'abc', d: 'def'}")}));
+TEST(ProjectionExecutorType, ShouldRejectMixOfInclusionAndBannedComputedFields) {
+ ASSERT_THROWS(
+ makeProjectionWithBannedComputedFields(BSON("a" << true << "b" << wrapInLiteral(1))),
+ AssertionException);
+
+ ASSERT_THROWS(
+ makeProjectionWithBannedComputedFields(BSON("a.b" << true << "a.c" << wrapInLiteral(1))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << true << "c" << wrapInLiteral(1)))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(BSON("a" << BSON("b" << true << "c"
+ << "stringLiteral"))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindSliceWithInclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{'a.b': {$slice: [1,2]}, c: 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: [2,3]}, c: 'abc'}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3]}, c: 'abc'}")}));
+TEST(ProjectionExecutorType, ShouldRejectOnlyComputedFieldsWhenComputedFieldsAreBanned) {
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << wrapInLiteral(1) << "b" << wrapInLiteral(2))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a.b" << wrapInLiteral(1) << "a.c" << wrapInLiteral(2))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2)))),
+ AssertionException);
+
+ ASSERT_THROWS(makeProjectionWithBannedComputedFields(
+ BSON("a" << BSON("b" << wrapInLiteral(1) << "c" << wrapInLiteral(2)))),
+ AssertionException);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindSliceSkipLimitWithInclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{'a.b': {$slice: [1,2]}, c: 1}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: [2,3]}, c: 'abc'}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3,4]}, c: 'abc'}")}));
+TEST(ProjectionExecutorType, ShouldAcceptInclusionProjectionWhenComputedFieldsAreBanned) {
+ auto parsedProject = makeProjectionWithBannedComputedFields(BSON("a" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false << "a" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false << "a.b.c" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id.x" << true));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << BSON("x" << true)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("x" << BSON("_id" << true)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
+}
+
+TEST(ProjectionExecutorType, ShouldAcceptExclusionProjectionWhenComputedFieldsAreBanned) {
+ auto parsedProject = makeProjectionWithBannedComputedFields(BSON("a" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id.x" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << BSON("x" << false)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("x" << BSON("_id" << false)));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
+
+ parsedProject = makeProjectionWithBannedComputedFields(BSON("_id" << false));
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
}
-TEST_F(ProjectionExecutorTest, CanProjectFindSliceBasicWithExclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{'a.b': {$slice: 3}, c: 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: [1,2,3]}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3,4]}, c: 'abc'}")}));
+TEST(ProjectionExecutorType, ShouldCoerceNumericsToBools) {
+ 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());
+ ASSERT(parsedProject->getType() ==
+ TransformerInterface::TransformerType::kExclusionProjection);
+ }
+
+ std::vector<Value> nonZeroes = {
+ Value(1), Value(-1), Value(3), Value(1LL), Value(1.0), Value(Decimal128(1))};
+ for (auto&& nonZero : nonZeroes) {
+ auto parsedProject = makeProjectionWithDefaultPolicies(Document{{"a", nonZero}}.toBson());
+ ASSERT(parsedProject->getType() ==
+ TransformerInterface::TransformerType::kInclusionProjection);
+ }
}
-TEST_F(ProjectionExecutorTest, CanProjectFindSliceSkipLimitWithExclusion) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{'a.b': {$slice: [1,2]}, c: 0}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: [2,3]}}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3,4]}, c: 'abc'}")}));
+TEST(ProjectionExecutorType, GetExpressionForPathGetsTopLevelExpression) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto projectObj = BSON("$add" << BSON_ARRAY(BSON("$const" << 1) << BSON("$const" << 3)));
+ auto expr = Expression::parseObject(expCtx, projectObj, expCtx->variablesParseState);
+ ProjectionPolicies defaultPolicies;
+ auto node = InclusionNode(defaultPolicies);
+ node.addExpressionForPath(FieldPath("key"), expr);
+ BSONObjBuilder bob;
+ ASSERT_EQ(expr, node.getExpressionForPath(FieldPath("key")));
}
-TEST_F(ProjectionExecutorTest, CanProjectFindSliceAndPositional) {
- auto proj = parseWithFindFeaturesEnabled(fromjson("{'a.b': {$slice: [1,2]}, 'c.$': 1}"),
- fromjson("{c: {$gte: 6}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: {b: [2,3]}, c: [6]}")},
- executor->applyTransformation(Document{fromjson("{a: {b: [1,2,3,4]}, c: [5,6,7]}")}));
+TEST(ProjectionExecutorType, GetExpressionForPathGetsCorrectTopLevelExpression) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto correctObj = BSON("$add" << BSON_ARRAY(BSON("$const" << 1) << BSON("$const" << 3)));
+ auto incorrectObj = BSON("$add" << BSON_ARRAY(BSON("$const" << 2) << BSON("$const" << 4)));
+ auto correctExpr = Expression::parseObject(expCtx, correctObj, expCtx->variablesParseState);
+ auto incorrectExpr = Expression::parseObject(expCtx, incorrectObj, expCtx->variablesParseState);
+ ProjectionPolicies defaultPolicies;
+ auto node = InclusionNode(defaultPolicies);
+ node.addExpressionForPath(FieldPath("key"), correctExpr);
+ node.addExpressionForPath(FieldPath("other"), incorrectExpr);
+ BSONObjBuilder bob;
+ ASSERT_EQ(correctExpr, node.getExpressionForPath(FieldPath("key")));
}
-TEST_F(ProjectionExecutorTest, ExecutorOptimizesExpression) {
- auto proj = parseWithDefaultPolicies(fromjson("{a: 1, b: {$add: [1, 2]}}"));
- auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
- ASSERT_DOCUMENT_EQ(Document{fromjson("{_id: true, a: true, b: {$const: 3}}")},
- executor->serializeTransformation(boost::none));
+TEST(ProjectionExecutorType, GetExpressionForPathGetsNonTopLevelExpression) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto projectObj = BSON("$add" << BSON_ARRAY(BSON("$const" << 1) << BSON("$const" << 3)));
+ auto expr = Expression::parseObject(expCtx, projectObj, expCtx->variablesParseState);
+ ProjectionPolicies defaultPolicies;
+ auto node = InclusionNode(defaultPolicies);
+ node.addExpressionForPath(FieldPath("key.second"), expr);
+ BSONObjBuilder bob;
+ ASSERT_EQ(expr, node.getExpressionForPath(FieldPath("key.second")));
}
+
} // namespace
} // namespace mongo::projection_executor