summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHana Pearlman <hana.pearlman@mongodb.com>2021-04-05 17:43:26 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-04-20 12:47:22 +0000
commitb4d072aec9f92014020c275a05860f425a9fc374 (patch)
tree755116ce296cffb9841a46dd9a227e3ced4f3d80
parent6d02b78de375778c860b9e48df998ecdce2630e6 (diff)
downloadmongo-b4d072aec9f92014020c275a05860f425a9fc374.tar.gz
SERVER-55668: Allow rewrite to push exclusion meta projections past $unpackBucket
-rw-r--r--src/mongo/db/exec/SConscript1
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.cpp61
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.h14
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor_test.cpp69
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor.cpp5
-rw-r--r--src/mongo/db/exec/projection_node.cpp5
-rw-r--r--src/mongo/db/exec/projection_node.h6
-rw-r--r--src/mongo/db/pipeline/SConscript1
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp37
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket.h8
-rw-r--r--src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/extract_project_for_pushdown_test.cpp128
-rw-r--r--src/mongo/db/pipeline/document_source_single_document_transformation.h11
-rw-r--r--src/mongo/db/pipeline/transformer_interface.h5
13 files changed, 342 insertions, 9 deletions
diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript
index 6a1ad9c6fdf..c0265d59013 100644
--- a/src/mongo/db/exec/SConscript
+++ b/src/mongo/db/exec/SConscript
@@ -86,6 +86,7 @@ env.Library(
target='projection_executor',
source=[
'add_fields_projection_executor.cpp',
+ 'exclusion_projection_executor.cpp',
'inclusion_projection_executor.cpp',
'projection_executor_builder.cpp',
'projection_executor_utils.cpp',
diff --git a/src/mongo/db/exec/exclusion_projection_executor.cpp b/src/mongo/db/exec/exclusion_projection_executor.cpp
new file mode 100644
index 00000000000..9823ed1b125
--- /dev/null
+++ b/src/mongo/db/exec/exclusion_projection_executor.cpp
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2021-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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/exec/exclusion_projection_executor.h"
+
+namespace mongo::projection_executor {
+
+std::pair<BSONObj, bool> ExclusionNode::extractProjectOnFieldAndRename(const StringData& oldName,
+ const StringData& newName) {
+ BSONObjBuilder extractedExclusion;
+
+ // Check for a projection directly on 'oldName'. For example, {oldName: 0}.
+ if (auto it = _projectedFields.find(oldName); it != _projectedFields.end()) {
+ extractedExclusion.append(newName, false);
+ _projectedFields.erase(it);
+ }
+
+ // Check for a projection on subfields of 'oldName'. For example, {oldName: {a: 0, b: 0}}.
+ if (auto it = _children.find(oldName); it != _children.end()) {
+ extractedExclusion.append(newName, it->second->serialize(boost::none).toBson());
+ _children.erase(it);
+ }
+
+ if (auto it = std::find(_orderToProcessAdditionsAndChildren.begin(),
+ _orderToProcessAdditionsAndChildren.end(),
+ oldName);
+ it != _orderToProcessAdditionsAndChildren.end()) {
+ _orderToProcessAdditionsAndChildren.erase(it);
+ }
+
+ return {extractedExclusion.obj(), _projectedFields.empty() && _children.empty()};
+}
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/exclusion_projection_executor.h b/src/mongo/db/exec/exclusion_projection_executor.h
index dd4854e39ea..a9c1fade72d 100644
--- a/src/mongo/db/exec/exclusion_projection_executor.h
+++ b/src/mongo/db/exec/exclusion_projection_executor.h
@@ -32,6 +32,7 @@
#include <memory>
#include <string>
+#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/exec/projection_node.h"
@@ -63,6 +64,14 @@ public:
}
}
+ /**
+ * Removes and returns a BSONObj representing the part of this project that depends only on
+ * 'oldName'. Also returns a bool indicating whether this entire project is extracted. In the
+ * extracted $project, 'oldName' is renamed to 'newName'. 'oldName' should not be dotted.
+ */
+ std::pair<BSONObj, bool> extractProjectOnFieldAndRename(const StringData& oldName,
+ const StringData& newName);
+
protected:
std::unique_ptr<ProjectionNode> makeChild(const std::string& fieldName) const final {
return std::make_unique<ExclusionNode>(
@@ -149,6 +158,11 @@ public:
return boost::none;
}
+ std::pair<BSONObj, bool> extractProjectOnFieldAndRename(const StringData& oldName,
+ const StringData& newName) final {
+ return _root->extractProjectOnFieldAndRename(oldName, newName);
+ }
+
private:
// The ExclusionNode tree does most of the execution work once constructed.
std::unique_ptr<ExclusionNode> _root;
diff --git a/src/mongo/db/exec/exclusion_projection_executor_test.cpp b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
index 9ddd696e861..81c63ad280f 100644
--- a/src/mongo/db/exec/exclusion_projection_executor_test.cpp
+++ b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
@@ -38,6 +38,7 @@
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/json.h"
+#include "mongo/bson/unordered_fields_bsonobj_comparator.h"
#include "mongo/db/exec/document_value/document.h"
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/exec/document_value/value.h"
@@ -546,5 +547,73 @@ TEST(ExclusionProjectionExecutionTest, ShouldNotRetainNestedArraysIfNoRecursionN
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
+
+//
+// extractProjectOnFieldAndRename() tests.
+//
+TEST(ExclusionProjectionExecutionTest, ShouldExtractEntireProjectOnRootField) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(fromjson("{a: 0}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("a", "b");
+
+ ASSERT_TRUE(allExtracted);
+ ASSERT_BSONOBJ_EQ(fromjson("{b: false}"), extractedProj);
+}
+
+TEST(ExclusionProjectionExecutionTest, ShouldExtractEntireProjectOnMultipleSubfields) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(fromjson("{'a.c': 0, 'a.d': 0}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("a", "b");
+
+ ASSERT_TRUE(allExtracted);
+ const UnorderedFieldsBSONObjComparator kComparator;
+ ASSERT_EQ(kComparator.compare(fromjson("{b: {c: false, d: false}}"), extractedProj), 0);
+}
+
+TEST(ExclusionProjectionExecutionTest, ShouldExtractPartOfProjectOnRootField) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(fromjson("{a: 0, c: 0}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("a", "b");
+
+ ASSERT_FALSE(allExtracted);
+ ASSERT_BSONOBJ_EQ(fromjson("{b: false}"), extractedProj);
+ ASSERT_BSONOBJ_EQ(fromjson("{c: false, _id: true}"),
+ exclusion->serializeTransformation(boost::none).toBson());
+}
+
+TEST(ExclusionProjectionExecutionTest, ShouldExtractPartOfProjectOnSubfields) {
+ auto exclusion =
+ makeExclusionProjectionWithDefaultPolicies(fromjson("{'x.y': 0, 'x.z': 0, 'c.d': 0}}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("x", "w");
+
+ ASSERT_FALSE(allExtracted);
+ const UnorderedFieldsBSONObjComparator kComparator;
+ ASSERT_EQ(kComparator.compare(fromjson("{w: {y: false, z: false}}"), extractedProj), 0);
+ ASSERT_BSONOBJ_EQ(fromjson("{c: {d: false}, _id: true}"),
+ exclusion->serializeTransformation(boost::none).toBson());
+}
+
+TEST(ExclusionProjectionExecutionTest, ShouldCorrectlyLeaveRemainderWithExcludedId) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(fromjson("{a: 0, _id: 0}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("a", "b");
+
+ ASSERT_FALSE(allExtracted);
+ ASSERT_BSONOBJ_EQ(fromjson("{b: false}"), extractedProj);
+ ASSERT_BSONOBJ_EQ(fromjson("{_id: false}"),
+ exclusion->serializeTransformation(boost::none).toBson());
+}
+
+TEST(ExclusionProjectionExecutionTest, ShouldNotExtractWhenFieldIsNotPresent) {
+ auto exclusion = makeExclusionProjectionWithDefaultPolicies(fromjson("{a: {b: 0}}"));
+
+ auto [extractedProj, allExtracted] = exclusion->extractProjectOnFieldAndRename("b", "c");
+
+ ASSERT_FALSE(allExtracted);
+ ASSERT_BSONOBJ_EQ(fromjson("{}"), extractedProj);
+ ASSERT_BSONOBJ_EQ(fromjson("{a: {b: false}, _id: true}"),
+ exclusion->serializeTransformation(boost::none).toBson());
+}
} // namespace
} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/inclusion_projection_executor.cpp b/src/mongo/db/exec/inclusion_projection_executor.cpp
index 81ec31c02b2..e330f868ca6 100644
--- a/src/mongo/db/exec/inclusion_projection_executor.cpp
+++ b/src/mongo/db/exec/inclusion_projection_executor.cpp
@@ -66,12 +66,11 @@ void FastPathEligibleInclusionNode::_applyProjections(BSONObj bson, BSONObjBuild
while (it.more() && nFieldsNeeded > 0) {
const auto bsonElement{it.next()};
const auto fieldName{bsonElement.fieldNameStringData()};
- const absl::string_view fieldNameKey{fieldName.rawData(), fieldName.size()};
- if (_projectedFields.find(fieldNameKey) != _projectedFields.end()) {
+ if (_projectedFields.find(fieldName) != _projectedFields.end()) {
bob->append(bsonElement);
--nFieldsNeeded;
- } else if (auto childIt = _children.find(fieldNameKey); childIt != _children.end()) {
+ } else if (auto childIt = _children.find(fieldName); childIt != _children.end()) {
auto child = static_cast<FastPathEligibleInclusionNode*>(childIt->second.get());
if (bsonElement.type() == BSONType::Object) {
diff --git a/src/mongo/db/exec/projection_node.cpp b/src/mongo/db/exec/projection_node.cpp
index 0cee88a78f6..1ef569ecd75 100644
--- a/src/mongo/db/exec/projection_node.cpp
+++ b/src/mongo/db/exec/projection_node.cpp
@@ -140,13 +140,12 @@ void ProjectionNode::applyProjections(const Document& inputDoc, MutableDocument*
while (it.more()) {
auto fieldName = it.fieldName();
- absl::string_view fieldNameKey{fieldName.rawData(), fieldName.size()};
- if (_projectedFields.find(fieldNameKey) != _projectedFields.end()) {
+ if (_projectedFields.find(fieldName) != _projectedFields.end()) {
outputProjectedField(
fieldName, applyLeafProjectionToValue(it.next().second), outputDoc);
++projectedFields;
- } else if (auto childIt = _children.find(fieldNameKey); childIt != _children.end()) {
+ } else if (auto childIt = _children.find(fieldName); childIt != _children.end()) {
outputProjectedField(
fieldName, childIt->second->applyProjectionsToValue(it.next().second), outputDoc);
++projectedFields;
diff --git a/src/mongo/db/exec/projection_node.h b/src/mongo/db/exec/projection_node.h
index e6a4feab973..2a587580330 100644
--- a/src/mongo/db/exec/projection_node.h
+++ b/src/mongo/db/exec/projection_node.h
@@ -160,9 +160,9 @@ protected:
// Writes the given value to the output doc, replacing the existing value of 'field' if present.
virtual void outputProjectedField(StringData field, Value val, MutableDocument* outDoc) const;
- stdx::unordered_map<std::string, std::unique_ptr<ProjectionNode>> _children;
- stdx::unordered_map<std::string, boost::intrusive_ptr<Expression>> _expressions;
- stdx::unordered_set<std::string> _projectedFields;
+ StringMap<std::unique_ptr<ProjectionNode>> _children;
+ StringMap<boost::intrusive_ptr<Expression>> _expressions;
+ StringSet _projectedFields;
ProjectionPolicies _policies;
std::string _pathToNode;
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index d470ef3a575..69ab349d19f 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -406,6 +406,7 @@ env.CppUnitTest(
'document_source_union_with_test.cpp',
'document_source_internal_unpack_bucket_test/extract_or_build_project_to_internalize_test.cpp',
'document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp',
+ 'document_source_internal_unpack_bucket_test/extract_project_for_pushdown_test.cpp',
'document_source_internal_unpack_bucket_test/group_reorder_test.cpp',
'document_source_internal_unpack_bucket_test/internalize_project_test.cpp',
'document_source_internal_unpack_bucket_test/pushdown_computed_meta_projections_test.cpp',
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
index b44a3b36d3b..fb53b7e951d 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp
@@ -622,6 +622,22 @@ DocumentSourceInternalUnpackBucket::splitMatchOnMetaAndRename(
return {nullptr, match};
}
+std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractProjectForPushDown(
+ DocumentSource* src) const {
+ if (auto nextProject = dynamic_cast<DocumentSourceSingleDocumentTransformation*>(src);
+ nextProject && _bucketUnpacker.bucketSpec().metaField) {
+ // If we have an exclusion project, we can push down just the parts on the metaField.
+ if (nextProject->getType() == TransformerInterface::TransformerType::kExclusionProjection) {
+ return nextProject->extractProjectOnFieldAndRename(
+ _bucketUnpacker.bucketSpec().metaField.get(), timeseries::kBucketMetaFieldName);
+ }
+
+ // TODO: SERVER-55727
+ }
+
+ return {BSONObj{}, false};
+}
+
Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimizeAt(
Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
invariant(*itr == this);
@@ -695,6 +711,27 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi
}
}
+ // TODO: SERVER-54766: replace this logic.
+ if (std::next(itr) == container->end()) {
+ return container->end();
+ }
+
+ // Attempt to push down a $project on the metaField past $_internalUnpackBucket.
+ if (auto [metaProject, deleteRemainder] = extractProjectForPushDown(std::next(itr)->get());
+ !metaProject.isEmpty()) {
+ container->insert(itr,
+ DocumentSourceProject::createFromBson(
+ BSON("$project" << metaProject).firstElement(), getContext()));
+
+ if (deleteRemainder) {
+ // We have pushed down the entire $project. Remove the old $project from the pipeline,
+ // then attempt to optimize this stage again.
+ container->erase(std::next(itr));
+ return std::prev(itr) == container->begin() ? std::prev(itr)
+ : std::prev(std::prev(itr));
+ }
+ }
+
// Attempt to extract computed meta projections from subsequent $project, $addFields, or $set
// and push them before the $_internalunpackBucket.
pushDownComputedMetaProjection(itr, container);
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
index af10e99b87e..066447c5b49 100644
--- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h
@@ -178,6 +178,14 @@ public:
bool pushDownComputedMetaProjection(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container);
+ /**
+ * If 'src' represents an exclusion $project, attempts to extract the parts of 'src' that are
+ * only on the metaField. Returns a BSONObj representing the extracted project and a bool
+ * indicating whether all of 'src' was extracted. In the extracted $project, the metaField is
+ * renamed from the user defined name to 'kBucketMetaFieldName'.
+ */
+ std::pair<BSONObj, bool> extractProjectForPushDown(DocumentSource* src) const;
+
private:
GetNextResult doGetNext() final;
diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/extract_project_for_pushdown_test.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/extract_project_for_pushdown_test.cpp
new file mode 100644
index 00000000000..73cefb13ad3
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/extract_project_for_pushdown_test.cpp
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2021-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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/bson/unordered_fields_bsonobj_comparator.h"
+#include "mongo/db/pipeline/aggregation_context_fixture.h"
+#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h"
+#include "mongo/db/pipeline/document_source_project.h"
+#include "mongo/db/pipeline/pipeline.h"
+#include "mongo/db/query/util/make_data_structure.h"
+#include "mongo/unittest/bson_test_util.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+using InternalUnpackBucketExtractProjectForPushdownTest = AggregationContextFixture;
+
+TEST_F(InternalUnpackBucketExtractProjectForPushdownTest, DoesNotExtractWhenNoMetaField) {
+ auto unpack = DocumentSourceInternalUnpackBucket::createFromBson(
+ fromjson("{$_internalUnpackBucket: { exclude: [], timeField: 'foo', bucketMaxSpanSeconds: "
+ "3600}}")
+ .firstElement(),
+ getExpCtx());
+ auto project = DocumentSourceProject::createFromBson(
+ fromjson("{$project: {meta: 0}}").firstElement(), getExpCtx());
+
+ auto [extractedProj, deleteRemainder] =
+ dynamic_cast<DocumentSourceInternalUnpackBucket*>(unpack.get())
+ ->extractProjectForPushDown(project.get());
+
+ ASSERT_TRUE(extractedProj.isEmpty());
+
+ ASSERT_FALSE(deleteRemainder);
+ std::vector<Value> serializedArray;
+ project->serializeToArray(serializedArray);
+ ASSERT_BSONOBJ_EQ(fromjson("{$project: {meta: false, _id: true}}"),
+ serializedArray[0].getDocument().toBson());
+}
+
+TEST_F(InternalUnpackBucketExtractProjectForPushdownTest, ExtractsEntireProjectOnMetaField) {
+ auto unpack = DocumentSourceInternalUnpackBucket::createFromBson(
+ fromjson("{$_internalUnpackBucket: { exclude: [], timeField: 'foo', metaField: 'myMeta', "
+ "bucketMaxSpanSeconds: 3600}}")
+ .firstElement(),
+ getExpCtx());
+ auto project = DocumentSourceProject::createFromBson(
+ fromjson("{$project: {myMeta: 0}}").firstElement(), getExpCtx());
+
+ auto [extractedProj, deleteRemainder] =
+ dynamic_cast<DocumentSourceInternalUnpackBucket*>(unpack.get())
+ ->extractProjectForPushDown(project.get());
+
+ ASSERT_BSONOBJ_EQ(fromjson("{meta: false}"), extractedProj);
+ ASSERT_TRUE(deleteRemainder);
+}
+
+TEST_F(InternalUnpackBucketExtractProjectForPushdownTest, ExtractsEntireProjectOnSubfieldsOfMeta) {
+ auto unpack = DocumentSourceInternalUnpackBucket::createFromBson(
+ fromjson("{$_internalUnpackBucket: { exclude: [], timeField: 'foo', metaField: 'myMeta', "
+ "bucketMaxSpanSeconds: 3600}}")
+ .firstElement(),
+ getExpCtx());
+ auto project = DocumentSourceProject::createFromBson(
+ fromjson("{$project: {myMeta: {a: 0, b: 0}}}").firstElement(), getExpCtx());
+
+ auto [extractedProj, deleteRemainder] =
+ dynamic_cast<DocumentSourceInternalUnpackBucket*>(unpack.get())
+ ->extractProjectForPushDown(project.get());
+
+ const UnorderedFieldsBSONObjComparator kComparator;
+ ASSERT_EQ(kComparator.compare(fromjson("{meta: {a: false, b: false}}"), extractedProj), 0);
+ ASSERT_TRUE(deleteRemainder);
+}
+
+TEST_F(InternalUnpackBucketExtractProjectForPushdownTest, ExtractsPartOfProjectOnMetaField) {
+ auto unpack = DocumentSourceInternalUnpackBucket::createFromBson(
+ fromjson("{$_internalUnpackBucket: { exclude: [], timeField: 'foo', metaField: 'myMeta', "
+ "bucketMaxSpanSeconds: 3600}}")
+ .firstElement(),
+ getExpCtx());
+ auto project = DocumentSourceProject::createFromBson(
+ fromjson("{$project: {a: 0, 'myMeta.a': 0, b: 0, 'myMeta.b': 0, c: 0}}").firstElement(),
+ getExpCtx());
+
+ auto [extractedProj, deleteRemainder] =
+ dynamic_cast<DocumentSourceInternalUnpackBucket*>(unpack.get())
+ ->extractProjectForPushDown(project.get());
+
+ const UnorderedFieldsBSONObjComparator kComparator;
+ ASSERT_EQ(kComparator.compare(fromjson("{meta: {a: false, b: false}}"), extractedProj), 0);
+
+ ASSERT_FALSE(deleteRemainder);
+ std::vector<Value> serializedArray;
+ project->serializeToArray(serializedArray);
+ ASSERT_EQ(kComparator.compare(fromjson("{$project: {_id: true, a: false, b: false, c: false}}"),
+ serializedArray[0].getDocument().toBson()),
+ 0);
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.h b/src/mongo/db/pipeline/document_source_single_document_transformation.h
index 158f9936fdc..8c7369e396d 100644
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.h
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.h
@@ -117,6 +117,17 @@ public:
return _parsedTransform->extractComputedProjections(oldName, newName, reservedNames);
}
+ /**
+ * If this transformation is a project, removes and returns a BSONObj representing the part of
+ * this project that depends only on 'oldName'. Also returns a bool indicating whether this
+ * entire project is extracted. In the extracted $project, 'oldName' is renamed to 'newName'.
+ * 'oldName' should not be dotted.
+ */
+ std::pair<BSONObj, bool> extractProjectOnFieldAndRename(const StringData& oldName,
+ const StringData& newName) {
+ return _parsedTransform->extractProjectOnFieldAndRename(oldName, newName);
+ }
+
protected:
GetNextResult doGetNext() final;
void doDispose() final;
diff --git a/src/mongo/db/pipeline/transformer_interface.h b/src/mongo/db/pipeline/transformer_interface.h
index 40ae3b0564e..6be98af2570 100644
--- a/src/mongo/db/pipeline/transformer_interface.h
+++ b/src/mongo/db/pipeline/transformer_interface.h
@@ -80,5 +80,10 @@ public:
const std::set<StringData>& reservedNames) {
return {BSONObj{}, false};
}
+
+ virtual std::pair<BSONObj, bool> extractProjectOnFieldAndRename(const StringData& oldName,
+ const StringData& newName) {
+ return {BSONObj{}, false};
+ }
};
} // namespace mongo