summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnton Korshunov <anton.korshunov@mongodb.com>2019-11-15 10:19:14 +0000
committerevergreen <evergreen@mongodb.com>2019-11-15 10:19:14 +0000
commit73b456d5c059b17d1c7f0f8badb7c72391ee2173 (patch)
tree89451cae5a7cfbedb3a2faccd307bc870f7b0a40 /src
parentcc3f2c8ba06e9e8c248a0d91a9efd5351311ca37 (diff)
downloadmongo-73b456d5c059b17d1c7f0f8badb7c72391ee2173.tar.gz
SERVER-43291 Consolidate projection execution interfaces
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/SConscript25
-rw-r--r--src/mongo/db/catalog/index_key_validate.cpp2
-rw-r--r--src/mongo/db/commands/mr_common.cpp12
-rw-r--r--src/mongo/db/exec/SConscript22
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor.cpp (renamed from src/mongo/db/pipeline/parsed_add_fields.cpp)129
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor.h (renamed from src/mongo/db/pipeline/parsed_add_fields.h)120
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor_test.cpp (renamed from src/mongo/db/pipeline/parsed_add_fields_test.cpp)186
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.h (renamed from src/mongo/db/pipeline/parsed_exclusion_projection.h)33
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor_test.cpp (renamed from src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp)20
-rw-r--r--src/mongo/db/exec/find_projection_executor_test.cpp476
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor.h (renamed from src/mongo/db/pipeline/parsed_inclusion_projection.h)47
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor_test.cpp (renamed from src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp)20
-rw-r--r--src/mongo/db/exec/projection.cpp1
-rw-r--r--src/mongo/db/exec/projection.h2
-rw-r--r--src/mongo/db/exec/projection_exec_agg.cpp176
-rw-r--r--src/mongo/db/exec/projection_exec_agg.h103
-rw-r--r--src/mongo/db/exec/projection_executor.h109
-rw-r--r--src/mongo/db/exec/projection_executor_builder.cpp (renamed from src/mongo/db/exec/projection_executor.cpp)24
-rw-r--r--src/mongo/db/exec/projection_executor_builder.h48
-rw-r--r--src/mongo/db/exec/projection_executor_builder_test.cpp241
-rw-r--r--src/mongo/db/exec/projection_executor_test.cpp682
-rw-r--r--src/mongo/db/exec/projection_executor_utils.cpp (renamed from src/mongo/db/exec/find_projection_executor.cpp)112
-rw-r--r--src/mongo/db/exec/projection_executor_utils.h (renamed from src/mongo/db/exec/find_projection_executor.h)52
-rw-r--r--src/mongo/db/exec/projection_executor_utils_test.cpp336
-rw-r--r--src/mongo/db/exec/projection_executor_wildcard_access_test.cpp (renamed from src/mongo/db/exec/projection_exec_agg_test.cpp)137
-rw-r--r--src/mongo/db/exec/projection_node.cpp (renamed from src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp)10
-rw-r--r--src/mongo/db/exec/projection_node.h (renamed from src/mongo/db/pipeline/parsed_aggregation_projection_node.h)10
-rw-r--r--src/mongo/db/index/SConscript3
-rw-r--r--src/mongo/db/index/wildcard_access_method.h6
-rw-r--r--src/mongo/db/index/wildcard_key_generator.cpp37
-rw-r--r--src/mongo/db/index/wildcard_key_generator.h14
-rw-r--r--src/mongo/db/pipeline/SConscript23
-rw-r--r--src/mongo/db/pipeline/document_source_add_fields.cpp7
-rw-r--r--src/mongo/db/pipeline/document_source_add_fields_test.cpp7
-rw-r--r--src/mongo/db/pipeline/document_source_project.cpp11
-rw-r--r--src/mongo/db/pipeline/document_source_project_test.cpp6
-rw-r--r--src/mongo/db/pipeline/expression_find_internal.h8
-rw-r--r--src/mongo/db/pipeline/expression_find_internal_test.cpp4
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.h138
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp612
-rw-r--r--src/mongo/db/pipeline/parsed_find_projection_test.cpp282
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp1
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/collection_query_info.cpp14
-rw-r--r--src/mongo/db/query/get_executor.cpp15
-rw-r--r--src/mongo/db/query/get_executor_test.cpp38
-rw-r--r--src/mongo/db/query/index_entry.h11
-rw-r--r--src/mongo/db/query/plan_cache_indexability.cpp4
-rw-r--r--src/mongo/db/query/plan_cache_indexability.h9
-rw-r--r--src/mongo/db/query/plan_cache_indexability_test.cpp6
-rw-r--r--src/mongo/db/query/plan_cache_test.cpp15
-rw-r--r--src/mongo/db/query/planner_ixselect_test.cpp8
-rw-r--r--src/mongo/db/query/planner_wildcard_helpers.cpp10
-rw-r--r--src/mongo/db/query/projection_policies.h24
-rw-r--r--src/mongo/db/query/query_planner_wildcard_index_test.cpp4
-rw-r--r--src/mongo/embedded/stitch_support/stitch_support.cpp4
56 files changed, 2110 insertions, 2347 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 7dcdda1cbf1..77eb23e934e 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -970,29 +970,6 @@ env.Library(
)
env.Library(
- target='projection_executor',
- source=[
- 'exec/find_projection_executor.cpp',
- 'exec/projection_executor.cpp',
- ],
- LIBDEPS=[
- 'matcher/expressions',
- 'pipeline/parsed_aggregation_projection',
- ],
-)
-
-env.Library(
- target='projection_exec_agg',
- source=[
- 'exec/projection_exec_agg.cpp'
- ],
- LIBDEPS=[
- 'projection_executor',
- 'query/projection_ast',
- ],
-)
-
-env.Library(
target='query_exec',
source=[
'clientcursor.cpp',
@@ -1078,6 +1055,7 @@ env.Library(
'cursor_server_params',
'db_raii',
'dbdirectclient',
+ 'exec/projection_executor',
'exec/scoped_timer',
'exec/sort_executor',
'exec/working_set',
@@ -1088,7 +1066,6 @@ env.Library(
'matcher/expressions_mongod_only',
'ops/parsed_update',
'pipeline/pipeline',
- 'projection_executor',
'query/query_common',
'query/query_planner',
'repl/repl_coordinator_interface',
diff --git a/src/mongo/db/catalog/index_key_validate.cpp b/src/mongo/db/catalog/index_key_validate.cpp
index 1c5c0f180de..c25fe90e159 100644
--- a/src/mongo/db/catalog/index_key_validate.cpp
+++ b/src/mongo/db/catalog/index_key_validate.cpp
@@ -430,7 +430,7 @@ StatusWith<BSONObj> validateIndexSpec(
try {
// We use WildcardKeyGenerator::createProjectionExec to parse and validate the path
// projection spec.
- WildcardKeyGenerator::createProjectionExec(key, indexSpecElem.embeddedObject());
+ WildcardKeyGenerator::createProjectionExecutor(key, indexSpecElem.embeddedObject());
} catch (const DBException& ex) {
return ex.toStatus(str::stream() << "Failed to parse: "
<< IndexDescriptor::kPathProjectionFieldName);
diff --git a/src/mongo/db/commands/mr_common.cpp b/src/mongo/db/commands/mr_common.cpp
index 7ac5056e5ac..10cb66b3543 100644
--- a/src/mongo/db/commands/mr_common.cpp
+++ b/src/mongo/db/commands/mr_common.cpp
@@ -39,6 +39,8 @@
#include "mongo/db/auth/privilege.h"
#include "mongo/db/catalog/document_validation.h"
#include "mongo/db/commands.h"
+#include "mongo/db/exec/inclusion_projection_executor.h"
+#include "mongo/db/exec/projection_node.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/pipeline/accumulator_js_reduce.h"
#include "mongo/db/pipeline/document_source.h"
@@ -51,8 +53,6 @@
#include "mongo/db/pipeline/document_source_sort.h"
#include "mongo/db/pipeline/document_source_unwind.h"
#include "mongo/db/pipeline/expression_javascript.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/log.h"
@@ -110,11 +110,11 @@ auto translateSort(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj
auto translateMap(boost::intrusive_ptr<ExpressionContext> expCtx, std::string code) {
auto emitExpression = ExpressionInternalJsEmit::create(
expCtx, ExpressionFieldPath::parse(expCtx, "$$ROOT", expCtx->variablesParseState), code);
- auto node = std::make_unique<parsed_aggregation_projection::InclusionNode>(
+ auto node = std::make_unique<projection_executor::InclusionNode>(
ProjectionPolicies{ProjectionPolicies::DefaultIdPolicy::kExcludeId});
node->addExpressionForPath(FieldPath{"emits"s}, std::move(emitExpression));
auto inclusion = std::unique_ptr<TransformerInterface>{
- std::make_unique<parsed_aggregation_projection::ParsedInclusionProjection>(
+ std::make_unique<projection_executor::InclusionProjectionExecutor>(
expCtx,
ProjectionPolicies{ProjectionPolicies::DefaultIdPolicy::kExcludeId},
std::move(node))};
@@ -145,12 +145,12 @@ auto translateFinalize(boost::intrusive_ptr<ExpressionContext> expCtx, std::stri
ExpressionFieldPath::parse(expCtx, "$_id", expCtx->variablesParseState),
ExpressionFieldPath::parse(expCtx, "$value", expCtx->variablesParseState))),
code);
- auto node = std::make_unique<parsed_aggregation_projection::InclusionNode>(
+ auto node = std::make_unique<projection_executor::InclusionNode>(
ProjectionPolicies{ProjectionPolicies::DefaultIdPolicy::kIncludeId});
node->addProjectionForPath(FieldPath{"_id"s});
node->addExpressionForPath(FieldPath{"value"s}, std::move(jsExpression));
auto inclusion = std::unique_ptr<TransformerInterface>{
- std::make_unique<parsed_aggregation_projection::ParsedInclusionProjection>(
+ std::make_unique<projection_executor::InclusionProjectionExecutor>(
expCtx,
ProjectionPolicies{ProjectionPolicies::DefaultIdPolicy::kIncludeId},
std::move(node))};
diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript
index d8c2926a43d..27dd9c4fee9 100644
--- a/src/mongo/db/exec/SConscript
+++ b/src/mongo/db/exec/SConscript
@@ -56,6 +56,19 @@ sortExecutorEnv.Library(
)
env.Library(
+ target='projection_executor',
+ source=[
+ 'add_fields_projection_executor.cpp',
+ 'projection_executor_builder.cpp',
+ 'projection_executor_utils.cpp',
+ 'projection_node.cpp'
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/db/matcher/expressions'
+ ],
+)
+
+env.Library(
target='stagedebug_cmd',
source=[
'stagedebug_cmd.cpp'
@@ -77,9 +90,14 @@ env.CppUnitTest(
"document_value/document_value_test.cpp",
"document_value/document_value_test_util_self_test.cpp",
"document_value/value_comparator_test.cpp",
+ "add_fields_projection_executor_test.cpp",
+ "exclusion_projection_executor_test.cpp",
"find_projection_executor_test.cpp",
- "projection_exec_agg_test.cpp",
+ "inclusion_projection_executor_test.cpp",
+ "projection_executor_builder_test.cpp",
"projection_executor_test.cpp",
+ "projection_executor_utils_test.cpp",
+ "projection_executor_wildcard_access_test.cpp",
"queued_data_stage_test.cpp",
"sort_test.cpp",
"working_set_test.cpp",
@@ -87,7 +105,6 @@ env.CppUnitTest(
LIBDEPS=[
"$BUILD_DIR/mongo/base",
"$BUILD_DIR/mongo/db/auth/authmocks",
- "$BUILD_DIR/mongo/db/projection_exec_agg",
"$BUILD_DIR/mongo/db/query/collation/collator_factory_mock",
"$BUILD_DIR/mongo/db/query/collation/collator_interface_mock",
"$BUILD_DIR/mongo/db/query/query_test_service_context",
@@ -98,6 +115,7 @@ env.CppUnitTest(
"$BUILD_DIR/mongo/util/clock_source_mock",
"document_value/document_value",
"document_value/document_value_test_util",
+ "projection_executor",
"working_set",
],
)
diff --git a/src/mongo/db/pipeline/parsed_add_fields.cpp b/src/mongo/db/exec/add_fields_projection_executor.cpp
index a4b9677da84..ec3acc136bc 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.cpp
+++ b/src/mongo/db/exec/add_fields_projection_executor.cpp
@@ -29,23 +29,104 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/pipeline/parsed_add_fields.h"
+#include "mongo/db/exec/add_fields_projection_executor.h"
#include <algorithm>
#include "mongo/db/matcher/expression_algo.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-
-namespace mongo {
-namespace parsed_aggregation_projection {
+namespace mongo::projection_executor {
+namespace {
using TransformerType = TransformerInterface::TransformerType;
-
using expression::isPathPrefixOf;
-//
-// ProjectionSpecValidator
-//
+/**
+ * This class ensures that the specification was valid: that none of the paths specified conflict
+ * with one another, that there is at least one field, etc. Here "projection" includes $addFields
+ * specifications.
+ */
+class ProjectionSpecValidator {
+public:
+ /**
+ * Throws if the specification is not valid for a projection. Because this validator is meant to
+ * be generic, the error thrown is generic. Callers at the DocumentSource level should modify
+ * the error message if they want to include information specific to the stage name used.
+ */
+ static void uassertValid(const BSONObj& spec);
+
+private:
+ ProjectionSpecValidator(const BSONObj& spec) : _rawObj(spec) {}
+
+ /**
+ * Uses '_seenPaths' to see if 'path' conflicts with any paths that have already been specified.
+ *
+ * For example, a user is not allowed to specify {'a': 1, 'a.b': 1}, or some similar conflicting
+ * paths.
+ */
+ void ensurePathDoesNotConflictOrThrow(const std::string& path);
+
+ /**
+ * Throws if an invalid projection specification is detected.
+ */
+ void validate();
+
+ /**
+ * Parses a single BSONElement. 'pathToElem' should include the field name of 'elem'.
+ *
+ * Delegates to parseSubObject() if 'elem' is an object. Otherwise adds the full path to 'elem'
+ * to '_seenPaths'.
+ *
+ * Calls ensurePathDoesNotConflictOrThrow with the path to this element, throws on conflicting
+ * path specifications.
+ */
+ void parseElement(const BSONElement& elem, const FieldPath& pathToElem);
+
+ /**
+ * Traverses 'thisLevelSpec', parsing each element in turn.
+ *
+ * Throws if any paths conflict with each other or existing paths, 'thisLevelSpec' contains a
+ * dotted path, or if 'thisLevelSpec' represents an invalid expression.
+ */
+ void parseNestedObject(const BSONObj& thisLevelSpec, const FieldPath& prefix);
+
+ // The original object. Used to generate more helpful error messages.
+ const BSONObj& _rawObj;
+
+ // Custom comparator that orders fieldpath strings by path prefix first, then by field.
+ struct PathPrefixComparator {
+ static constexpr char dot = '.';
+
+ // Returns true if the lhs value should sort before the rhs, false otherwise.
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ for (size_t pos = 0, len = std::min(lhs.size(), rhs.size()); pos < len; ++pos) {
+ auto &lchar = lhs[pos], &rchar = rhs[pos];
+ if (lchar == rchar) {
+ continue;
+ }
+
+ // Consider the path delimiter '.' as being less than all other characters, so that
+ // paths sort directly before any paths they prefix and directly after any paths
+ // which prefix them.
+ if (lchar == dot) {
+ return true;
+ } else if (rchar == dot) {
+ return false;
+ }
+
+ // Otherwise, default to normal character comparison.
+ return lchar < rchar;
+ }
+
+ // If we get here, then we have reached the end of lhs and/or rhs and all of their path
+ // segments up to this point match. If lhs is shorter than rhs, then lhs prefixes rhs
+ // and should sort before it.
+ return lhs.size() < rhs.size();
+ }
+ };
+
+ // Tracks which paths we've seen to ensure no two paths conflict with each other.
+ std::set<std::string, PathPrefixComparator> _seenPaths;
+};
void ProjectionSpecValidator::uassertValid(const BSONObj& spec) {
ProjectionSpecValidator(spec).validate();
@@ -127,19 +208,20 @@ void ProjectionSpecValidator::parseNestedObject(const BSONObj& thisLevelSpec,
parseElement(elem, FieldPath::getFullyQualifiedPath(prefix.fullPath(), fieldName));
}
}
+} // namespace
-std::unique_ptr<ParsedAddFields> ParsedAddFields::create(
+std::unique_ptr<AddFieldsProjectionExecutor> AddFieldsProjectionExecutor::create(
const boost::intrusive_ptr<ExpressionContext>& expCtx, const BSONObj& spec) {
// Verify that we don't have conflicting field paths, etc.
ProjectionSpecValidator::uassertValid(spec);
- std::unique_ptr<ParsedAddFields> parsedAddFields = std::make_unique<ParsedAddFields>(expCtx);
+ auto executor = std::make_unique<AddFieldsProjectionExecutor>(expCtx);
// Actually parse the specification.
- parsedAddFields->parse(spec);
- return parsedAddFields;
+ executor->parse(spec);
+ return executor;
}
-void ParsedAddFields::parse(const BSONObj& spec) {
+void AddFieldsProjectionExecutor::parse(const BSONObj& spec) {
for (auto elem : spec) {
auto fieldName = elem.fieldNameStringData();
@@ -170,7 +252,7 @@ void ParsedAddFields::parse(const BSONObj& spec) {
}
}
-Document ParsedAddFields::applyProjection(const Document& inputDoc) const {
+Document AddFieldsProjectionExecutor::applyProjection(const Document& inputDoc) const {
// The output doc is the same as the input doc, with the added fields.
MutableDocument output(inputDoc);
_root->applyExpressions(inputDoc, &output);
@@ -180,9 +262,10 @@ Document ParsedAddFields::applyProjection(const Document& inputDoc) const {
return output.freeze();
}
-bool ParsedAddFields::parseObjectAsExpression(StringData pathToObject,
- const BSONObj& objSpec,
- const VariablesParseState& variablesParseState) {
+bool AddFieldsProjectionExecutor::parseObjectAsExpression(
+ StringData pathToObject,
+ const BSONObj& objSpec,
+ const VariablesParseState& variablesParseState) {
if (objSpec.firstElementFieldName()[0] == '$') {
// This is an expression like {$add: [...]}. We already verified that it has only one field.
invariant(objSpec.nFields() == 1);
@@ -193,9 +276,9 @@ bool ParsedAddFields::parseObjectAsExpression(StringData pathToObject,
return false;
}
-void ParsedAddFields::parseSubObject(const BSONObj& subObj,
- const VariablesParseState& variablesParseState,
- InclusionNode* node) {
+void AddFieldsProjectionExecutor::parseSubObject(const BSONObj& subObj,
+ const VariablesParseState& variablesParseState,
+ InclusionNode* node) {
for (auto&& elem : subObj) {
invariant(elem.fieldName()[0] != '$');
// Dotted paths in a sub-object have already been detected and disallowed by the function
@@ -221,6 +304,4 @@ void ParsedAddFields::parseSubObject(const BSONObj& subObj,
}
}
}
-
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_add_fields.h b/src/mongo/db/exec/add_fields_projection_executor.h
index 5ea10e55c4b..e17ce25e494 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.h
+++ b/src/mongo/db/exec/add_fields_projection_executor.h
@@ -31,130 +31,37 @@
#include <memory>
+#include "mongo/db/exec/inclusion_projection_executor.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
-namespace mongo {
-namespace parsed_aggregation_projection {
+namespace mongo::projection_executor {
/**
- * This class ensures that the specification was valid: that none of the paths specified conflict
- * with one another, that there is at least one field, etc. Here "projection" includes $addFields
- * specifications.
- */
-class ProjectionSpecValidator {
-public:
- /**
- * Throws if the specification is not valid for a projection. Because this validator is meant to
- * be generic, the error thrown is generic. Callers at the DocumentSource level should modify
- * the error message if they want to include information specific to the stage name used.
- */
- static void uassertValid(const BSONObj& spec);
-
-private:
- ProjectionSpecValidator(const BSONObj& spec) : _rawObj(spec) {}
-
- /**
- * Uses '_seenPaths' to see if 'path' conflicts with any paths that have already been specified.
- *
- * For example, a user is not allowed to specify {'a': 1, 'a.b': 1}, or some similar conflicting
- * paths.
- */
- void ensurePathDoesNotConflictOrThrow(const std::string& path);
-
- /**
- * Throws if an invalid projection specification is detected.
- */
- void validate();
-
- /**
- * Parses a single BSONElement. 'pathToElem' should include the field name of 'elem'.
- *
- * Delegates to parseSubObject() if 'elem' is an object. Otherwise adds the full path to 'elem'
- * to '_seenPaths'.
- *
- * Calls ensurePathDoesNotConflictOrThrow with the path to this element, throws on conflicting
- * path specifications.
- */
- void parseElement(const BSONElement& elem, const FieldPath& pathToElem);
-
- /**
- * Traverses 'thisLevelSpec', parsing each element in turn.
- *
- * Throws if any paths conflict with each other or existing paths, 'thisLevelSpec' contains a
- * dotted path, or if 'thisLevelSpec' represents an invalid expression.
- */
- void parseNestedObject(const BSONObj& thisLevelSpec, const FieldPath& prefix);
-
- // The original object. Used to generate more helpful error messages.
- const BSONObj& _rawObj;
-
- // Custom comparator that orders fieldpath strings by path prefix first, then by field.
- struct PathPrefixComparator {
- static constexpr char dot = '.';
-
- // Returns true if the lhs value should sort before the rhs, false otherwise.
- bool operator()(const std::string& lhs, const std::string& rhs) const {
- for (size_t pos = 0, len = std::min(lhs.size(), rhs.size()); pos < len; ++pos) {
- auto &lchar = lhs[pos], &rchar = rhs[pos];
- if (lchar == rchar) {
- continue;
- }
-
- // Consider the path delimiter '.' as being less than all other characters, so that
- // paths sort directly before any paths they prefix and directly after any paths
- // which prefix them.
- if (lchar == dot) {
- return true;
- } else if (rchar == dot) {
- return false;
- }
-
- // Otherwise, default to normal character comparison.
- return lchar < rchar;
- }
-
- // If we get here, then we have reached the end of lhs and/or rhs and all of their path
- // segments up to this point match. If lhs is shorter than rhs, then lhs prefixes rhs
- // and should sort before it.
- return lhs.size() < rhs.size();
- }
- };
-
- // Tracks which paths we've seen to ensure no two paths conflict with each other.
- std::set<std::string, PathPrefixComparator> _seenPaths;
-};
-
-/**
- * A ParsedAddFields represents a parsed form of the raw BSON specification for the AddFields
- * stage.
+ * A AddFieldsProjectionExecutor represents a projection execution tree for the $addFields stage.
*
* This class is mostly a wrapper around an InclusionNode tree. It contains logic to parse a
* specification object into the corresponding InclusionNode tree, but defers most execution logic
- * to the underlying tree. In this way it is similar to ParsedInclusionProjection, but it differs
+ * to the underlying tree. In this way it is similar to InclusionProjectionExecutor, but it differs
* by not applying inclusions before adding computed fields, thus keeping all existing fields.
*/
-class ParsedAddFields : public ParsedAggregationProjection {
+class AddFieldsProjectionExecutor : public ProjectionExecutor {
public:
/**
- * TODO SERVER-25510: The ParsedAggregationProjection _id and array-recursion policies are not
+ * TODO SERVER-25510: The ProjectionExecutor _id and array-recursion policies are not
* applicable to the $addFields "projection" stage. We make them non-configurable here.
*/
- ParsedAddFields(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ParsedAggregationProjection(
- expCtx,
- {ProjectionPolicies::DefaultIdPolicy::kIncludeId,
- ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays,
- ProjectionPolicies::ComputedFieldsPolicy::kAllowComputedFields}),
+ AddFieldsProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx)
+ : ProjectionExecutor(expCtx,
+ {ProjectionPolicies::DefaultIdPolicy::kIncludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays,
+ ProjectionPolicies::ComputedFieldsPolicy::kAllowComputedFields}),
_root(new InclusionNode(_policies)) {}
/**
* Creates the data needed to perform an AddFields.
* Verifies that there are no conflicting paths in the specification.
- * Overrides the ParsedAggregationProjection's create method.
*/
- static std::unique_ptr<ParsedAddFields> create(
+ static std::unique_ptr<AddFieldsProjectionExecutor> create(
const boost::intrusive_ptr<ExpressionContext>& expCtx, const BSONObj& spec);
TransformerType getType() const final {
@@ -234,5 +141,4 @@ private:
// The InclusionNode tree does most of the execution work once constructed.
std::unique_ptr<InclusionNode> _root;
};
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_add_fields_test.cpp b/src/mongo/db/exec/add_fields_projection_executor_test.cpp
index 318eb84b9ef..047bbbbc42e 100644
--- a/src/mongo/db/pipeline/parsed_add_fields_test.cpp
+++ b/src/mongo/db/exec/add_fields_projection_executor_test.cpp
@@ -29,13 +29,12 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/pipeline/parsed_add_fields.h"
-
#include <vector>
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/json.h"
+#include "mongo/db/exec/add_fields_projection_executor.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"
@@ -43,88 +42,96 @@
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/unittest/unittest.h"
-namespace mongo {
-namespace parsed_aggregation_projection {
+namespace mongo::projection_executor {
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.
+// These AddFieldsProjectionExecutor spec tests are a subset of the ProjectionExecutor creation
+// tests. AddFieldsProjectionExecutor 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) {
+// Verify that AddFieldsProjectionExecutor rejects specifications with conflicting field paths.
+TEST(AddFieldsProjectionExecutorSpec, ThrowsOnCreationWithConflictingFieldPaths) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// These specs contain the same exact path.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << 1 << "a" << 2)), AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("b" << 1 << "b" << 2))),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a" << 1 << "a" << 2)),
AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("_id" << 3 << "_id" << true)),
+ ASSERT_THROWS(
+ AddFieldsProjectionExecutor::create(expCtx, BSON("a" << BSON("b" << 1 << "b" << 2))),
+ AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("_id" << 3 << "_id" << true)),
AssertionException);
// These specs contain overlapping paths.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << 1 << "a.b" << 2)),
- AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a.b.c" << 1 << "a" << 2)),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a" << 1 << "a.b" << 2)),
AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("_id" << true << "_id.x" << true)),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a.b.c" << 1 << "a" << 2)),
AssertionException);
+ ASSERT_THROWS(
+ AddFieldsProjectionExecutor::create(expCtx, BSON("_id" << true << "_id.x" << true)),
+ AssertionException);
}
-// Verify that ParsedAddFields rejects specifications that contain invalid field paths.
-TEST(ParsedAddFieldsSpec, ThrowsOnCreationWithInvalidFieldPath) {
+// Verify that AddFieldsProjectionExecutor rejects specifications that contain invalid field paths.
+TEST(AddFieldsProjectionExecutorSpec, ThrowsOnCreationWithInvalidFieldPath) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Dotted subfields are not allowed.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("b.c" << true))),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a" << BSON("b.c" << true))),
AssertionException);
// The user cannot start a field with $.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("$dollar" << 0)), AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("c.$d" << true)), AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("$dollar" << 0)),
+ AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("c.$d" << true)),
+ AssertionException);
// Empty field names should throw an error.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("" << 2)), AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSON("" << true))),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("" << 2)), AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a" << BSON("" << true))),
+ AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("" << BSON("a" << true))),
AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("" << BSON("a" << true))),
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a." << true)),
+ AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON(".a" << true)),
AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a." << true)), AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON(".a" << true)), AssertionException);
}
-// Verify that ParsedAddFields rejects specifications that contain empty objects or invalid
-// expressions.
-TEST(ParsedAddFieldsSpec, ThrowsOnCreationWithInvalidObjectsOrExpressions) {
+// Verify that AddFieldsProjectionExecutor rejects specifications that contain empty objects or
+// invalid expressions.
+TEST(AddFieldsProjectionExecutorSpec, ThrowsOnCreationWithInvalidObjectsOrExpressions) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
// Invalid expressions should be rejected.
- ASSERT_THROWS(ParsedAddFields::create(
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(
expCtx, BSON("a" << BSON("$add" << BSON_ARRAY(4 << 2) << "b" << 1))),
AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(expCtx,
- BSON("a" << BSON("$gt" << BSON("bad"
- << "arguments")))),
- AssertionException);
- ASSERT_THROWS(ParsedAddFields::create(
+ ASSERT_THROWS(
+ AddFieldsProjectionExecutor::create(expCtx,
+ BSON("a" << BSON("$gt" << BSON("bad"
+ << "arguments")))),
+ AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(
expCtx, BSON("a" << false << "b" << BSON("$unknown" << BSON_ARRAY(4 << 2)))),
AssertionException);
// Empty specifications are not allowed.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSONObj()), AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSONObj()), AssertionException);
// Empty nested objects are not allowed.
- ASSERT_THROWS(ParsedAddFields::create(expCtx, BSON("a" << BSONObj())), AssertionException);
+ ASSERT_THROWS(AddFieldsProjectionExecutor::create(expCtx, BSON("a" << BSONObj())),
+ AssertionException);
}
-TEST(ParsedAddFields, DoesNotErrorOnTwoNestedFields) {
+TEST(AddFieldsProjectionExecutor, DoesNotErrorOnTwoNestedFields) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields::create(expCtx, BSON("a.b" << true << "a.c" << true));
- ParsedAddFields::create(expCtx, BSON("a.b" << true << "a" << BSON("c" << true)));
+ AddFieldsProjectionExecutor::create(expCtx, BSON("a.b" << true << "a.c" << true));
+ AddFieldsProjectionExecutor::create(expCtx, BSON("a.b" << true << "a" << BSON("c" << true)));
}
// Verify that replaced fields are not included as dependencies.
-TEST(ParsedAddFieldsDeps, RemovesReplaceFieldsFromDependencies) {
+TEST(AddFieldsProjectionExecutorDeps, RemovesReplaceFieldsFromDependencies) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a" << true));
DepsTracker deps;
@@ -136,9 +143,9 @@ TEST(ParsedAddFieldsDeps, RemovesReplaceFieldsFromDependencies) {
}
// Verify that adding nested fields keeps the top-level field as a dependency.
-TEST(ParsedAddFieldsDeps, IncludesTopLevelFieldInDependenciesWhenAddingNestedFields) {
+TEST(AddFieldsProjectionExecutorDeps, IncludesTopLevelFieldInDependenciesWhenAddingNestedFields) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("x.y" << true));
DepsTracker deps;
@@ -151,9 +158,9 @@ TEST(ParsedAddFieldsDeps, IncludesTopLevelFieldInDependenciesWhenAddingNestedFie
}
// Verify that fields that an expression depends on are added to the dependencies.
-TEST(ParsedAddFieldsDeps, AddsDependenciesForComputedFields) {
+TEST(AddFieldsProjectionExecutorDeps, AddsDependenciesForComputedFields) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("x.y"
<< "$z"
<< "a"
@@ -172,9 +179,9 @@ TEST(ParsedAddFieldsDeps, AddsDependenciesForComputedFields) {
// Verify that the serialization produces the correct output: converting numbers and literals to
// their corresponding $const form.
-TEST(ParsedAddFieldsSerialize, SerializesToCorrectForm) {
+TEST(AddFieldsProjectionExecutorSerialize, SerializesToCorrectForm) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(fromjson("{a: {$add: ['$a', 2]}, b: {d: 3}, 'x.y': {$literal: 4}}"));
auto expectedSerialization = Document(
@@ -191,9 +198,9 @@ TEST(ParsedAddFieldsSerialize, SerializesToCorrectForm) {
}
// Verify that serialize treats the _id field as any other field: including when explicity included.
-TEST(ParsedAddFieldsSerialize, AddsIdToSerializeWhenExplicitlyIncluded) {
+TEST(AddFieldsProjectionExecutorSerialize, AddsIdToSerializeWhenExplicitlyIncluded) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("_id" << false));
// Adds explicit "_id" setting field, serializes expressions.
@@ -213,9 +220,9 @@ TEST(ParsedAddFieldsSerialize, AddsIdToSerializeWhenExplicitlyIncluded) {
// 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) {
+TEST(AddFieldsProjectionExecutorSerialize, OmitsIdFromSerializeWhenNotIncluded) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a" << true));
// Does not implicitly include "_id" field.
@@ -232,9 +239,9 @@ TEST(ParsedAddFieldsSerialize, OmitsIdFromSerializeWhenNotIncluded) {
}
// Verify that the $addFields stage optimizes expressions into simpler forms when possible.
-TEST(ParsedAddFieldsOptimize, OptimizesTopLevelExpressions) {
+TEST(AddFieldsProjectionExecutorOptimize, OptimizesTopLevelExpressions) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a" << BSON("$add" << BSON_ARRAY(1 << 2))));
addition.optimize();
auto expectedSerialization = Document{{"a", Document{{"$const", 3}}}};
@@ -250,9 +257,9 @@ TEST(ParsedAddFieldsOptimize, OptimizesTopLevelExpressions) {
}
// Verify that the $addFields stage optimizes expressions even when they are nested.
-TEST(ParsedAddFieldsOptimize, ShouldOptimizeNestedExpressions) {
+TEST(AddFieldsProjectionExecutorOptimize, ShouldOptimizeNestedExpressions) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a.b" << BSON("$add" << BSON_ARRAY(1 << 2))));
addition.optimize();
auto expectedSerialization = Document{{"a", Document{{"b", Document{{"$const", 3}}}}}};
@@ -272,9 +279,9 @@ TEST(ParsedAddFieldsOptimize, ShouldOptimizeNestedExpressions) {
//
// Verify that a new field is added to the end of the document.
-TEST(ParsedAddFieldsExecutionTest, AddsNewFieldToEndOfDocument) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AddsNewFieldToEndOfDocument) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("c" << 3));
// There are no fields in the document.
@@ -289,9 +296,9 @@ TEST(ParsedAddFieldsExecutionTest, AddsNewFieldToEndOfDocument) {
}
// Verify that an existing field is replaced and stays in the same order in the document.
-TEST(ParsedAddFieldsExecutionTest, ReplacesFieldThatAlreadyExistsInDocument) {
+TEST(AddFieldsProjectionExecutorExecutionTest, ReplacesFieldThatAlreadyExistsInDocument) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("c" << 3));
// Specified field is the only field in the document, and is replaced.
@@ -306,9 +313,10 @@ TEST(ParsedAddFieldsExecutionTest, ReplacesFieldThatAlreadyExistsInDocument) {
}
// Verify that replacing multiple fields preserves the original field order in the document.
-TEST(ParsedAddFieldsExecutionTest, ReplacesMultipleFieldsWhilePreservingInputFieldOrder) {
+TEST(AddFieldsProjectionExecutorExecutionTest,
+ ReplacesMultipleFieldsWhilePreservingInputFieldOrder) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("second"
<< "SECOND"
<< "first"
@@ -319,9 +327,9 @@ TEST(ParsedAddFieldsExecutionTest, ReplacesMultipleFieldsWhilePreservingInputFie
}
// Verify that adding multiple fields adds the fields in the order specified.
-TEST(ParsedAddFieldsExecutionTest, AddsNewFieldsAfterExistingFieldsInOrderSpecified) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AddsNewFieldsAfterExistingFieldsInOrderSpecified) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("firstComputed"
<< "FIRST"
<< "secondComputed"
@@ -337,9 +345,10 @@ TEST(ParsedAddFieldsExecutionTest, AddsNewFieldsAfterExistingFieldsInOrderSpecif
// Verify that both adding and replacing fields at the same time follows the same rules as doing
// each independently.
-TEST(ParsedAddFieldsExecutionTest, ReplacesAndAddsNewFieldsWithSameOrderingRulesAsSeparately) {
+TEST(AddFieldsProjectionExecutorExecutionTest,
+ ReplacesAndAddsNewFieldsWithSameOrderingRulesAsSeparately) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("firstComputed"
<< "FIRST"
<< "second"
@@ -352,9 +361,9 @@ TEST(ParsedAddFieldsExecutionTest, ReplacesAndAddsNewFieldsWithSameOrderingRules
// 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) {
+TEST(AddFieldsProjectionExecutorExecutionTest, IdFieldIsKeptInOrderItAppearsInInputDocument) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("newField"
<< "computedVal"));
auto result = addition.applyProjection(Document{{"_id", "ID"_sd}, {"a", 1}});
@@ -367,9 +376,9 @@ TEST(ParsedAddFieldsExecutionTest, IdFieldIsKeptInOrderItAppearsInInputDocument)
}
// Verify that replacing or adding _id works just like any other field.
-TEST(ParsedAddFieldsExecutionTest, ShouldReplaceIdWithComputedId) {
+TEST(AddFieldsProjectionExecutorExecutionTest, ShouldReplaceIdWithComputedId) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("_id"
<< "newId"));
auto result = addition.applyProjection(Document{{"_id", "ID"_sd}, {"a", 1}});
@@ -390,9 +399,10 @@ TEST(ParsedAddFieldsExecutionTest, ShouldReplaceIdWithComputedId) {
//
// Verify that adding a dotted field keeps the other fields in the subdocument.
-TEST(ParsedAddFieldsExecutionTest, KeepsExistingSubFieldsWhenAddingSimpleDottedFieldToSubDoc) {
+TEST(AddFieldsProjectionExecutorExecutionTest,
+ KeepsExistingSubFieldsWhenAddingSimpleDottedFieldToSubDoc) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a.b" << true));
// More than one field in sub document.
@@ -417,9 +427,9 @@ TEST(ParsedAddFieldsExecutionTest, KeepsExistingSubFieldsWhenAddingSimpleDottedF
}
// Verify that creating a dotted field creates the subdocument structure necessary.
-TEST(ParsedAddFieldsExecutionTest, CreatesSubDocIfDottedAddedFieldDoesNotExist) {
+TEST(AddFieldsProjectionExecutorExecutionTest, CreatesSubDocIfDottedAddedFieldDoesNotExist) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("sub.target" << true));
// Should add the path if it doesn't exist.
@@ -435,9 +445,9 @@ TEST(ParsedAddFieldsExecutionTest, CreatesSubDocIfDottedAddedFieldDoesNotExist)
// 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) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AppliesDottedAdditionToEachElementInArray) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a.b" << true));
vector<Value> nestedValues = {Value(1),
@@ -461,9 +471,9 @@ TEST(ParsedAddFieldsExecutionTest, AppliesDottedAdditionToEachElementInArray) {
}
// Verify that creation of the subdocument structure works for many layers of nesting.
-TEST(ParsedAddFieldsExecutionTest, CreatesNestedSubDocumentsAllTheWayToAddedField) {
+TEST(AddFieldsProjectionExecutorExecutionTest, CreatesNestedSubDocumentsAllTheWayToAddedField) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a.b.c.d"
<< "computedVal"));
@@ -479,9 +489,9 @@ TEST(ParsedAddFieldsExecutionTest, CreatesNestedSubDocumentsAllTheWayToAddedFiel
}
// Verify that _id is not special: we can add subfields to it as well.
-TEST(ParsedAddFieldsExecutionTest, AddsSubFieldsOfId) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AddsSubFieldsOfId) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("_id.X" << true << "_id.Z"
<< "NEW"));
auto result = addition.applyProjection(Document{{"_id", Document{{"X", 1}, {"Y", 2}}}});
@@ -491,9 +501,9 @@ TEST(ParsedAddFieldsExecutionTest, AddsSubFieldsOfId) {
// Verify that both ways of specifying nested fields -- both dotted notation and nesting --
// can be used together in the same specification.
-TEST(ParsedAddFieldsExecutionTest, ShouldAllowMixedNestedAndDottedFields) {
+TEST(AddFieldsProjectionExecutorExecutionTest, ShouldAllowMixedNestedAndDottedFields) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor 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"
@@ -522,9 +532,9 @@ TEST(ParsedAddFieldsExecutionTest, ShouldAllowMixedNestedAndDottedFields) {
}
// Verify that adding nested fields preserves the addition order in the spec.
-TEST(ParsedAddFieldsExecutionTest, AddsNestedAddedFieldsInOrderSpecified) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AddsNestedAddedFieldsInOrderSpecified) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("b.d"
<< "FIRST"
<< "b.c"
@@ -539,9 +549,9 @@ TEST(ParsedAddFieldsExecutionTest, AddsNestedAddedFieldsInOrderSpecified) {
//
// Verify that the metadata is kept from the original input document.
-TEST(ParsedAddFieldsExecutionTest, AlwaysKeepsMetadataFromOriginalDoc) {
+TEST(AddFieldsProjectionExecutorExecutionTest, AlwaysKeepsMetadataFromOriginalDoc) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- ParsedAddFields addition(expCtx);
+ AddFieldsProjectionExecutor addition(expCtx);
addition.parse(BSON("a" << true));
MutableDocument inputDocBuilder(Document{{"a", 1}});
@@ -555,7 +565,5 @@ TEST(ParsedAddFieldsExecutionTest, AlwaysKeepsMetadataFromOriginalDoc) {
expectedDoc.copyMetaDataFrom(inputDoc);
ASSERT_DOCUMENT_EQ(result, expectedDoc.freeze());
}
-
} // namespace
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection.h b/src/mongo/db/exec/exclusion_projection_executor.h
index 65592682f33..a025d693e26 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection.h
+++ b/src/mongo/db/exec/exclusion_projection_executor.h
@@ -32,18 +32,10 @@
#include <memory>
#include <string>
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
-#include "mongo/stdx/unordered_map.h"
-#include "mongo/stdx/unordered_set.h"
-
-namespace mongo {
-
-class FieldPath;
-class Value;
-
-namespace parsed_aggregation_projection {
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_node.h"
+namespace mongo::projection_executor {
/**
* A node used to define the parsed structure of an exclusion projection. Each ExclusionNode
* represents one 'level' of the parsed specification. The root ExclusionNode represents all top
@@ -79,17 +71,16 @@ protected:
};
/**
- * A ParsedExclusionProjection represents a parsed form of the raw BSON specification.
+ * A ExclusionProjectionExecutor represents an execution tree for an exclusion projection.
*
- * This class is mostly a wrapper around an ExclusionNode tree. It contains logic to parse a
- * specification object into the corresponding ExclusionNode tree, but defers most execution logic
- * to the underlying tree.
+ * This class is mostly a wrapper around an ExclusionNode tree and defers most execution logic to
+ * the underlying tree.
*/
-class ParsedExclusionProjection : public ParsedAggregationProjection {
+class ExclusionProjectionExecutor : public ProjectionExecutor {
public:
- ParsedExclusionProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies)
- : ParsedAggregationProjection(expCtx, policies), _root(new ExclusionNode(_policies)) {}
+ ExclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies)
+ : ProjectionExecutor(expCtx, policies), _root(new ExclusionNode(_policies)) {}
TransformerType getType() const final {
return TransformerType::kExclusionProjection;
@@ -138,6 +129,4 @@ private:
// The ExclusionNode tree does most of the execution work once constructed.
std::unique_ptr<ExclusionNode> _root;
};
-
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
index 72a9276d7c1..59d1748d5fe 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
+++ b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
@@ -29,7 +29,7 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/pipeline/parsed_exclusion_projection.h"
+#include "mongo/db/exec/exclusion_projection_executor.h"
#include <iostream>
#include <iterator>
@@ -42,33 +42,32 @@
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/projection_parser.h"
#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
-namespace mongo {
-namespace parsed_aggregation_projection {
+namespace mongo::projection_executor {
namespace {
-
using std::vector;
auto createProjectionExecutor(const BSONObj& spec, const ProjectionPolicies& policies) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto projection = projection_ast::parse(expCtx, spec, policies);
- auto executor = projection_executor::buildProjectionExecutor(
- expCtx, &projection, policies, true /* optimizeExecutor */);
+ auto executor =
+ buildProjectionExecutor(expCtx, &projection, policies, true /* optimizeExecutor */);
invariant(executor->getType() == TransformerInterface::TransformerType::kExclusionProjection);
return executor;
}
-// Helper to simplify the creation of a ParsedExclusionProjection with default policies.
+// Helper to simplify the creation of a ExclusionProjectionExecutor with default policies.
auto makeExclusionProjectionWithDefaultPolicies(const BSONObj& spec) {
return createProjectionExecutor(spec, {});
}
-// Helper to simplify the creation of a ParsedExclusionProjection which excludes _id by default.
+// Helper to simplify the creation of a ExclusionProjectionExecutor which excludes _id by default.
auto makeExclusionProjectionWithDefaultIdExclusion(const BSONObj& spec) {
ProjectionPolicies defaultExcludeId{ProjectionPolicies::DefaultIdPolicy::kExcludeId,
ProjectionPolicies::kArrayRecursionPolicyDefault,
@@ -76,7 +75,7 @@ auto makeExclusionProjectionWithDefaultIdExclusion(const BSONObj& spec) {
return createProjectionExecutor(spec, defaultExcludeId);
}
-// Helper to simplify the creation of a ParsedExclusionProjection which does not recurse arrays.
+// Helper to simplify the creation of a ExclusionProjectionExecutor which does not recurse arrays.
auto makeExclusionProjectionWithNoArrayRecursion(const BSONObj& spec) {
ProjectionPolicies noArrayRecursion{
ProjectionPolicies::kDefaultIdPolicyDefault,
@@ -454,5 +453,4 @@ TEST(ExclusionProjectionExecutionTest, ShouldNotRetainNestedArraysIfNoRecursionN
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
} // namespace
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/find_projection_executor_test.cpp b/src/mongo/db/exec/find_projection_executor_test.cpp
index 39194fbdd93..e3e7d98ff97 100644
--- a/src/mongo/db/exec/find_projection_executor_test.cpp
+++ b/src/mongo/db/exec/find_projection_executor_test.cpp
@@ -30,289 +30,253 @@
#include "mongo/platform/basic.h"
#include "mongo/db/exec/document_value/document_value_test_util.h"
-#include "mongo/db/exec/find_projection_executor.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/unittest/death_test.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
+#include "mongo/db/pipeline/aggregation_context_fixture.h"
+#include "mongo/db/pipeline/expression_find_internal.h"
+#include "mongo/db/query/projection_parser.h"
#include "mongo/unittest/unittest.h"
-namespace mongo {
-namespace projection_executor {
-namespace positional_projection_tests {
-/**
- * Applies a find()-style positional projection at the given 'path' using 'matchSpec' to create
- * a 'MatchExpression' to match an element on the first array in the 'path'. If no value for
- * 'postImage' is provided, then the post-image used will be the value passed for the 'preImage'.
- */
-auto applyPositional(const BSONObj& matchSpec,
- const std::string& path,
- const Document& preImage,
- boost::optional<Document> postImage = boost::none) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, expCtx));
- return projection_executor::applyPositionalProjection(
- preImage, postImage.value_or(preImage), *matchExpr, path);
-}
-
-TEST(PositionalProjection, CorrectlyProjectsSimplePath) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: 1, foo: [6]}")},
- applyPositional(fromjson("{bar: 1, foo: {$gte: 5}}"),
- "foo",
- Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
-}
-
-TEST(PositionalProjection, CorrectlyProjectsDottedPath) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1, x: {y: [6]}}")},
- applyPositional(fromjson("{a: 1, 'x.y': {$gte: 5}}"),
- "x.y",
- Document{fromjson("{a: 1, x: {y: [1,2,6,10]}}")}));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1, x: {y: {z: [6]}}}")},
- applyPositional(fromjson("{a: 1, 'x.y.z': {$gte: 5}}"),
- "x.y.z",
- Document{fromjson("{a: 1, x: {y: {z: [1,2,6,10]}}}")}));
-}
-
-TEST(PositionalProjection, ProjectsValueUnmodifiedIfFieldIsNotArray) {
- auto doc = Document{fromjson("{foo: 3}")};
- ASSERT_DOCUMENT_EQ(doc, applyPositional(fromjson("{foo: 3}"), "foo", doc));
-}
+namespace mongo::projection_executor {
+constexpr auto kProjectionPostImageVarName =
+ projection_executor::ProjectionExecutor::kProjectionPostImageVarName;
-TEST(PositionalProjection, FailsToProjectPositionalPathComponentsForNestedArrays) {
- ASSERT_THROWS_CODE(applyPositional(fromjson("{'x.0.y': 42}"),
- "x.0.y",
- Document{fromjson("{x: [{y: [11, 42]}]}")}),
- AssertionException,
- 51247);
+auto createProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const BSONObj& projSpec,
+ ProjectionPolicies policies) {
+ auto projection = projection_ast::parse(expCtx, projSpec, policies);
+ return projection_executor::buildProjectionExecutor(
+ expCtx, &projection, policies, true /* optimizeExecutor */);
}
-TEST(PositionalProjection, CorrectlyProjectsNestedArrays) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [1,2]}]}")},
- applyPositional(fromjson("{'a.b': 1}"),
- "a",
- Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3,4]}]}")},
- applyPositional(fromjson("{'a.b': 3}"),
- "a",
- Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3,4]}]}")},
- applyPositional(fromjson("{'a.b': 3}"),
- "a.b",
- Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [['d','e','f']]}")},
- applyPositional(fromjson("{a: {$gt: ['a','b','c']}}"),
- "a",
- Document{fromjson("{a: [['a','b','c'],['d','e','f']]}")}));
-}
-
-TEST(PositionalProjection, FailsToProjectWithMultipleConditionsOnArray) {
- ASSERT_THROWS_CODE(applyPositional(fromjson("{$or: [{'x.y': 1}, {'x.y': 2}]}"),
- "x",
- Document{fromjson("{x: [{y: [1,2]}]}")}),
- AssertionException,
- 51246);
-}
-
-TEST(PositionalProjection, CanMergeWithExistingFieldsInOutputDocument) {
- auto doc = Document{fromjson("{foo: {bar: [1,2,6,10]}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{foo: {bar: [6]}}")},
- applyPositional(fromjson("{'foo.bar': {$gte: 5}}"), "foo.bar", doc));
-
- doc = Document{fromjson("{bar: 1, foo: {bar: [1,2,6,10]}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: 1, foo: {bar: [6]}}")},
- applyPositional(fromjson("{bar: 1, 'foo.bar': {$gte: 5}}"), "foo.bar", doc));
-
- doc = Document{fromjson("{bar: 1, foo: 3}")};
- ASSERT_DOCUMENT_EQ(doc, applyPositional(fromjson("{foo: 3}"), "foo", doc));
-}
-
-TEST(PositionalProjection, AppliesMatchExpressionToPreImageAndStoresResultInPostImage) {
- auto preImage = Document{fromjson("{foo: 1, bar: [1,2,6,10]}")};
- auto postImage = Document{fromjson("{bar: [1,2,6,10]}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: [6]}")},
- applyPositional(fromjson("{foo: 1, bar: 6}"), "bar", preImage, postImage));
-}
-} // namespace positional_projection_tests
-
-namespace elem_match_projection_tests {
-auto applyElemMatch(const BSONObj& match, const std::string& path, const Document& input) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto matchObj = BSON(path << BSON("$elemMatch" << match));
- auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchObj, expCtx));
- return projection_executor::applyElemMatchProjection(input, *matchExpr, path);
-}
+class PositionalProjectionExecutionTest : public AggregationContextFixture {
+protected:
+ auto applyPositional(const BSONObj& projSpec,
+ const BSONObj& matchSpec,
+ const std::string& path,
+ const Document& input) {
+ auto executor = createProjectionExecutor(getExpCtx(), projSpec, {});
+ auto matchExpr = CopyableMatchExpression{matchSpec,
+ getExpCtx(),
+ std::make_unique<ExtensionsCallbackNoop>(),
+ MatchExpressionParser::kBanAllSpecialFeatures};
+ auto expr = make_intrusive<ExpressionInternalFindPositional>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ path,
+ std::move(matchExpr));
+ executor->setRootReplacementExpression(expr);
+ return executor->applyTransformation(input);
+ }
+};
+
+class SliceProjectionExecutionTest : public AggregationContextFixture {
+protected:
+ auto applySlice(const BSONObj& projSpec,
+ const std::string& path,
+ boost::optional<int> skip,
+ int limit,
+ const Document& input) {
+ auto executor = createProjectionExecutor(getExpCtx(), projSpec, {});
+ auto expr = make_intrusive<ExpressionInternalFindSlice>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ path,
+ skip,
+ limit);
+ executor->setRootReplacementExpression(expr);
+ return executor->applyTransformation(input);
+ }
+};
+
+TEST_F(PositionalProjectionExecutionTest, CanApplyPositionalWithInclusionProjection) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{foo: [6]}")},
+ applyPositional(fromjson("{foo: 1}"),
+ fromjson("{foo: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{foo: [1,2,6,10]}")}));
-TEST(ElemMatchProjection, CorrectlyProjectsNonObjectElement) {
- ASSERT_VALUE_EQ(
- Document{fromjson("{foo: [4]}")}["foo"],
- applyElemMatch(fromjson("{$in: [4]}"), "foo", Document{fromjson("{foo: [1,2,3,4]}")}));
- ASSERT_VALUE_EQ(
- Document{fromjson("{foo: [4]}")}["foo"],
- applyElemMatch(fromjson("{$nin: [1,2,3]}"), "foo", Document{fromjson("{foo: [1,2,3,4]}")}));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{bar:1, foo: [6]}")},
+ applyPositional(fromjson("{bar: 1, foo: 1}"),
+ fromjson("{bar: 1, foo: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
}
-TEST(ElemMatchProjection, CorrectlyProjectsObjectElement) {
- ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
- applyElemMatch(fromjson("{bar: {$gte: 5}}"),
- "foo",
- Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
- "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+TEST_F(PositionalProjectionExecutionTest, AppliesProjectionToPreImage) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{b: [6], c: 'abc'}")},
+ applyPositional(fromjson("{b: 1, c: 1}"),
+ fromjson("{a: 1, b: {$gte: 5}}"),
+ "b",
+ Document{fromjson("{a: 1, b: [1,2,6,10], c: 'abc'}")}));
}
-TEST(ElemMatchProjection, CorrectlyProjectsArrayElement) {
- ASSERT_VALUE_EQ(Document{fromjson("{foo: [[3,4]]}")}["foo"],
- applyElemMatch(fromjson("{$gt: [1,2]}"),
- "foo",
- Document{fromjson("{foo: [[1,2], [3,4]]}")}));
+TEST_F(PositionalProjectionExecutionTest, ShouldAddInclusionFieldsAndWholeDocumentToDependencies) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
+ auto matchSpec = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
+ auto matchExpr = CopyableMatchExpression{matchSpec,
+ getExpCtx(),
+ std::make_unique<ExtensionsCallbackNoop>(),
+ MatchExpressionParser::kBanAllSpecialFeatures};
+ auto expr = make_intrusive<ExpressionInternalFindPositional>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ std::move(matchExpr));
+ executor->setRootReplacementExpression(expr);
+
+ DepsTracker deps;
+ executor->addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 2UL);
+ ASSERT_EQ(deps.fields.count("bar"), 1UL);
+ ASSERT_EQ(deps.fields.count("foo.bar"), 1UL);
+ ASSERT(deps.needWholeDocument);
}
-TEST(ElemMatchProjection, ProjectsAsEmptyDocumentIfInputIsEmpty) {
- ASSERT_VALUE_EQ({}, applyElemMatch(fromjson("{bar: {$gte: 5}}"), "foo", {}));
+TEST_F(PositionalProjectionExecutionTest, ShouldConsiderAllPathsAsModified) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
+ auto matchSpec = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
+ auto matchExpr = CopyableMatchExpression{matchSpec,
+ getExpCtx(),
+ std::make_unique<ExtensionsCallbackNoop>(),
+ MatchExpressionParser::kBanAllSpecialFeatures};
+ auto expr = make_intrusive<ExpressionInternalFindPositional>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ std::move(matchExpr));
+ executor->setRootReplacementExpression(expr);
+
+ auto modifiedPaths = executor->getModifiedPaths();
+ ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
}
-TEST(ElemMatchProjection, RemovesFieldFromOutputDocumentIfUnableToMatchArrayElement) {
- ASSERT_VALUE_EQ({},
- applyElemMatch(fromjson("{bar: {$gte: 5}}"),
- "foo",
- Document{fromjson("{foo: [{bar: 1, z: 1}, "
- "{bar: 2, z: 2}]}")}));
- ASSERT_VALUE_EQ(
- {},
- applyElemMatch(fromjson("{bar: {$gte: 20}}"),
- "foo",
- Document{fromjson("{bar: 1, foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
- "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+TEST_F(SliceProjectionExecutionTest, CanApplySliceWithInclusionProjection) {
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{foo: [1,2]}")},
+ applySlice(
+ fromjson("{foo: 1}"), "foo", boost::none, 2, Document{fromjson("{foo: [1,2,6,10]}")}));
+
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{bar:1, foo: [6]}")},
+ applySlice(fromjson("{bar: 1, foo: 1}"),
+ "foo",
+ 2,
+ 1,
+ Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
}
-TEST(ElemMatchProjection, CorrectlyProjectsWithMultipleCriteriaInMatchExpression) {
- ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 2, z: 2}]}")}["foo"],
- applyElemMatch(fromjson("{bar: {$gt: 1, $lt: 6}}"),
- "foo",
- Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
- "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+TEST_F(SliceProjectionExecutionTest, AppliesProjectionToPostImage) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{b: [1,2], c: 'abc'}")},
+ applySlice(fromjson("{b: 1, c: 1}"),
+ "b",
+ boost::none,
+ 2,
+ Document{fromjson("{a: 1, b: [1,2,6,10], c: 'abc'}")}));
}
-TEST(ElemMatchProjection, CanMergeWithExistingFieldsInInputDocument) {
- ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
- applyElemMatch(fromjson("{bar: {$gte: 5}}"),
- "foo",
- Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
- "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+TEST_F(SliceProjectionExecutionTest, CanApplySliceAndPositionalProjectionsTogether) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{foo: 1, bar: 1}"), {});
+ auto matchSpec = fromjson("{foo: {$gte: 3}}");
+ auto matchExpr = CopyableMatchExpression{matchSpec,
+ getExpCtx(),
+ std::make_unique<ExtensionsCallbackNoop>(),
+ MatchExpressionParser::kBanAllSpecialFeatures};
+ auto positionalExpr = make_intrusive<ExpressionInternalFindPositional>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo",
+ std::move(matchExpr));
+ auto sliceExpr =
+ make_intrusive<ExpressionInternalFindSlice>(getExpCtx(), positionalExpr, "bar", 1, 1);
+ executor->setRootReplacementExpression(sliceExpr);
- ASSERT_VALUE_EQ(
- Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
- applyElemMatch(fromjson("{bar: {$gte: 5}}"),
- "foo",
- Document{fromjson("{bar: 1, foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
- "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{foo: [3], bar: [6]}")},
+ executor->applyTransformation(Document{fromjson("{foo: [1,2,3,4], bar: [5,6,7,8]}")}));
}
-TEST(ElemMatchProjection, RertursEmptyValuefItContainsNumericSubfield) {
- ASSERT_VALUE_EQ(
- {}, applyElemMatch(fromjson("{$gt: 2}"), "foo", Document{BSON("foo" << BSON(0 << 3))}));
-
- ASSERT_VALUE_EQ({},
- applyElemMatch(fromjson("{$gt: 2}"),
- "foo",
- Document{BSON("bar" << 1 << "foo" << BSON(0 << 3))}));
+TEST_F(SliceProjectionExecutionTest, CanApplySliceWithExclusionProjection) {
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{foo: [6]}")},
+ applySlice(
+ fromjson("{bar: 0}"), "foo", 2, 1, Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
}
-} // namespace elem_match_projection_tests
-namespace slice_projection_tests {
-DEATH_TEST(SliceProjection,
- ShouldFailIfNegativeLimitSpecifiedWithPositiveSkip,
- "Invariant failure limit >= 0") {
- auto doc = Document{fromjson("{a: [1,2,3,4]}")};
- applySliceProjection(doc, "a", 1, -1);
+TEST_F(SliceProjectionExecutionTest,
+ ShouldAddFieldsAndWholeDocumentToDependenciesWithInclusionProjection) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
+ auto expr = make_intrusive<ExpressionInternalFindSlice>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ 1,
+ 1);
+ executor->setRootReplacementExpression(expr);
+
+ DepsTracker deps;
+ executor->addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 1UL);
+ ASSERT_EQ(deps.fields.count("bar"), 1UL);
+ ASSERT(deps.needWholeDocument);
}
-DEATH_TEST(SliceProjection,
- ShouldFailIfNegativeLimitSpecifiedWithNegativeSkip,
- "Invariant failure limit >= 0") {
- auto doc = Document{fromjson("{a: [1,2,3,4]}")};
- applySliceProjection(doc, "a", -1, -1);
+TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithInclusionProjection) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1}"), {});
+ auto expr = make_intrusive<ExpressionInternalFindSlice>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ 1,
+ 1);
+ executor->setRootReplacementExpression(expr);
+
+ auto modifiedPaths = executor->getModifiedPaths();
+ ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
}
-TEST(SliceProjection, CorrectlyProjectsSimplePath) {
- auto doc = Document{fromjson("{a: [1,2,3,4]}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2,3]}")},
- applySliceProjection(doc, "a", boost::none, 3));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [2,3,4]}")},
- applySliceProjection(doc, "a", boost::none, -3));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [2]}")}, applySliceProjection(doc, "a", -3, 1));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [2,3,4]}")}, applySliceProjection(doc, "a", -3, 4));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [4]}")}, applySliceProjection(doc, "a", 3, 1));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2,3,4]}")}, applySliceProjection(doc, "a", -5, 5));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: []}")}, applySliceProjection(doc, "a", 5, 2));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2]}")}, applySliceProjection(doc, "a", -5, 2));
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: [1,2,3,4]}")},
- applySliceProjection(doc, "a", boost::none, std::numeric_limits<int>::max()));
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: [1,2,3,4]}")},
- applySliceProjection(doc, "a", boost::none, std::numeric_limits<int>::min()));
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: [1,2,3,4]}")},
- applySliceProjection(
- doc, "a", std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{a: []}")},
- applySliceProjection(
- doc, "a", std::numeric_limits<int>::max(), std::numeric_limits<int>::max()));
-
- doc = Document{fromjson("{a: [{b: 1, c: 1}, {b: 2, c: 2}, {b: 3, c: 3}], d: 2}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 1, c: 1}], d: 2}")},
- applySliceProjection(doc, "a", boost::none, 1));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 3, c: 3}], d: 2}")},
- applySliceProjection(doc, "a", boost::none, -1));
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 2, c: 2}], d: 2}")},
- applySliceProjection(doc, "a", 1, 1));
-
- doc = Document{fromjson("{a: 1}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1}")},
- applySliceProjection(doc, "a", boost::none, 2));
-
- doc = Document{fromjson("{a: {b: 1}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: 1}}")},
- applySliceProjection(doc, "a", boost::none, 2));
+TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithExclusionProjection) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 0}"), {});
+ auto expr = make_intrusive<ExpressionInternalFindSlice>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ 1,
+ 1);
+ executor->setRootReplacementExpression(expr);
+
+ auto modifiedPaths = executor->getModifiedPaths();
+ ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
}
-TEST(SliceProjection, CorrectlyProjectsDottedPath) {
- auto doc = Document{fromjson("{a: {b: [1,2,3], c: 1}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [1,2], c: 1}}")},
- applySliceProjection(doc, "a.b", boost::none, 2));
-
- doc = Document{fromjson("{a: {b: [1,2,3], c: 1}, d: 1}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [1,2], c: 1}, d: 1}")},
- applySliceProjection(doc, "a.b", boost::none, 2));
-
- doc = Document{fromjson("{a: {b: [[1,2], [3,4], [5,6]]}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [[1,2], [3,4]]}}")},
- applySliceProjection(doc, "a.b", boost::none, 2));
-
- doc = Document{fromjson("{a: [{b: {c: [1,2,3,4]}}, {b: {c: [5,6,7,8]}}], d: 1}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: {c: [4]}}, {b: {c: [8]}}], d: 1}")},
- applySliceProjection(doc, "a.b.c", -1, 2));
-
- doc = Document{fromjson("{a: {b: 1}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: 1}}")},
- applySliceProjection(doc, "a.b", boost::none, 2));
-
- doc = Document{fromjson("{a: {b: {c: 1}}}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: {c: 1}}}")},
- applySliceProjection(doc, "a.b", boost::none, 2));
-
- doc = Document{fromjson("{a: [{b: [1,2,3], c: 1}]}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3], c: 1}]}")},
- applySliceProjection(doc, "a.b", boost::none, -1));
-
- doc = Document{fromjson("{a: [{b: [1,2,3], c: 4}, {b: [5,6,7], c: 8}]}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3], c: 4}, {b: [7], c: 8}]}")},
- applySliceProjection(doc, "a.b", boost::none, -1));
-
- doc = Document{fromjson("{a: [{b: [{x:1, c: [1, 2]}, {y: 1, c: [3, 4]}]}], z: 1}")};
- ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [{x:1, c: [1]}, {y: 1, c: [3]}]}], z: 1}")},
- applySliceProjection(doc, "a.b.c", boost::none, 1));
+TEST_F(SliceProjectionExecutionTest, ShouldAddWholeDocumentToDependenciesWithExclusionProjection) {
+ auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 0}"), {});
+ auto expr = make_intrusive<ExpressionInternalFindSlice>(
+ getExpCtx(),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
+ "foo.bar",
+ 1,
+ 1);
+ executor->setRootReplacementExpression(expr);
+
+ DepsTracker deps;
+ executor->addDependencies(&deps);
+
+ ASSERT_EQ(deps.fields.size(), 0UL);
+ ASSERT(deps.needWholeDocument);
}
-} // namespace slice_projection_tests
-} // namespace projection_executor
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.h b/src/mongo/db/exec/inclusion_projection_executor.h
index 7bc69dd8cad..4b8769b44f2 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.h
+++ b/src/mongo/db/exec/inclusion_projection_executor.h
@@ -31,20 +31,10 @@
#include <memory>
-#include "mongo/db/pipeline/expression.h"
-#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
-#include "mongo/stdx/unordered_map.h"
-#include "mongo/stdx/unordered_set.h"
-
-namespace mongo {
-
-class FieldPath;
-class Value;
-
-namespace parsed_aggregation_projection {
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_node.h"
+namespace mongo::projection_executor {
/**
* A node used to define the parsed structure of an inclusion projection. Each InclusionNode
* represents one 'level' of the parsed specification. The root InclusionNode represents all top
@@ -103,22 +93,22 @@ protected:
};
/**
- * A ParsedInclusionProjection represents a parsed form of the raw BSON specification.
+ * A InclusionProjectionExecutor represents an execution tree for an inclusion projection.
*
- * This class is mostly a wrapper around an InclusionNode tree. It contains logic to parse a
- * specification object into the corresponding InclusionNode tree, but defers most execution logic
- * to the underlying tree.
+ * This class is mostly a wrapper around an InclusionNode tree and defers most execution logic to
+ * the underlying tree.
*/
-class ParsedInclusionProjection : public ParsedAggregationProjection {
+class InclusionProjectionExecutor : public ProjectionExecutor {
public:
- ParsedInclusionProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies,
- std::unique_ptr<InclusionNode> root)
- : ParsedAggregationProjection(expCtx, policies), _root(std::move(root)) {}
-
- ParsedInclusionProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies)
- : ParsedInclusionProjection(expCtx, policies, std::make_unique<InclusionNode>(policies)) {}
+ InclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies,
+ std::unique_ptr<InclusionNode> root)
+ : ProjectionExecutor(expCtx, policies), _root(std::move(root)) {}
+
+ InclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies)
+ : InclusionProjectionExecutor(expCtx, policies, std::make_unique<InclusionNode>(policies)) {
+ }
TransformerType getType() const final {
return TransformerType::kInclusionProjection;
@@ -151,7 +141,7 @@ public:
* Optimize any computed expressions.
*/
void optimize() final {
- ParsedAggregationProjection::optimize();
+ ProjectionExecutor::optimize();
_root->optimize();
}
@@ -199,5 +189,4 @@ private:
// The InclusionNode tree does most of the execution work once constructed.
std::unique_ptr<InclusionNode> _root;
};
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
index c7dcf9f080b..b9df51e21ba 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection_test.cpp
+++ b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
@@ -29,7 +29,7 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
+#include "mongo/db/exec/inclusion_projection_executor.h"
#include <vector>
@@ -40,16 +40,15 @@
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/projection_parser.h"
#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
-namespace mongo {
-namespace parsed_aggregation_projection {
+namespace mongo::projection_executor {
namespace {
-
using std::vector;
template <typename T>
@@ -60,18 +59,18 @@ BSONObj wrapInLiteral(const T& arg) {
auto createProjectionExecutor(const BSONObj& spec, const ProjectionPolicies& policies) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto projection = projection_ast::parse(expCtx, spec, policies);
- auto executor = projection_executor::buildProjectionExecutor(
- expCtx, &projection, policies, true /* optimizeExecutor */);
+ auto executor =
+ buildProjectionExecutor(expCtx, &projection, policies, true /* optimizeExecutor */);
invariant(executor->getType() == TransformerInterface::TransformerType::kInclusionProjection);
return executor;
}
-// Helper to simplify the creation of a ParsedInclusionProjection with default policies.
+// Helper to simplify the creation of a InclusionProjectionExecutor with default policies.
auto makeInclusionProjectionWithDefaultPolicies(BSONObj spec) {
return createProjectionExecutor(spec, {});
}
-// Helper to simplify the creation of a ParsedInclusionProjection which excludes _id by default.
+// Helper to simplify the creation of a InclusionProjectionExecutor which excludes _id by default.
auto makeInclusionProjectionWithDefaultIdExclusion(BSONObj spec) {
ProjectionPolicies defaultExcludeId{ProjectionPolicies::DefaultIdPolicy::kExcludeId,
ProjectionPolicies::kArrayRecursionPolicyDefault,
@@ -79,7 +78,7 @@ auto makeInclusionProjectionWithDefaultIdExclusion(BSONObj spec) {
return createProjectionExecutor(spec, defaultExcludeId);
}
-// Helper to simplify the creation of a ParsedInclusionProjection which does not recurse arrays.
+// Helper to simplify the creation of a InclusionProjectionExecutor which does not recurse arrays.
auto makeInclusionProjectionWithNoArrayRecursion(BSONObj spec) {
ProjectionPolicies noArrayRecursion{
ProjectionPolicies::kDefaultIdPolicyDefault,
@@ -805,5 +804,4 @@ TEST(InclusionProjectionExecutionTest, ComputedFieldShouldReplaceNestedArrayForN
ASSERT_DOCUMENT_EQ(result, expectedResult);
}
} // namespace
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp
index 13e48a44c4c..cc46b688dbc 100644
--- a/src/mongo/db/exec/projection.cpp
+++ b/src/mongo/db/exec/projection.cpp
@@ -36,6 +36,7 @@
#include "mongo/db/exec/document_value/document.h"
#include "mongo/db/exec/plan_stage.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/exec/scoped_timer.h"
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/jsobj.h"
diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h
index 0866dbdd150..3e69e7f6e1e 100644
--- a/src/mongo/db/exec/projection.h
+++ b/src/mongo/db/exec/projection.h
@@ -104,7 +104,7 @@ private:
// True, if the projection contains a recordId $meta expression.
const bool _wantRecordId;
const projection_ast::ProjectType _projectType;
- std::unique_ptr<parsed_aggregation_projection::ParsedAggregationProjection> _executor;
+ std::unique_ptr<projection_executor::ProjectionExecutor> _executor;
};
/**
diff --git a/src/mongo/db/exec/projection_exec_agg.cpp b/src/mongo/db/exec/projection_exec_agg.cpp
deleted file mode 100644
index 94eafdafb7f..00000000000
--- a/src/mongo/db/exec/projection_exec_agg.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-/**
- * Copyright (C) 2018-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/projection_exec_agg.h"
-
-#include "mongo/db/exec/document_value/document.h"
-#include "mongo/db/exec/projection_executor.h"
-#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/query/projection_parser.h"
-#include "mongo/db/query/projection_policies.h"
-
-namespace mongo {
-
-class ProjectionExecAgg::ProjectionExecutor {
-public:
- using ParsedAggregationProjection = parsed_aggregation_projection::ParsedAggregationProjection;
-
- using TransformerType = TransformerInterface::TransformerType;
-
- ProjectionExecutor(BSONObj projSpec,
- DefaultIdPolicy defaultIdPolicy,
- ArrayRecursionPolicy arrayRecursionPolicy) {
- // Construct a dummy ExpressionContext for ParsedAggregationProjection. It's OK to set the
- // ExpressionContext's OperationContext and CollatorInterface to 'nullptr' here; since we
- // ban computed fields from the projection, the ExpressionContext will never be used.
- boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(nullptr, nullptr));
-
- // Default projection behaviour is to include _id if the projection spec omits it. If the
- // caller has specified that we should *exclude* _id by default, do so here. We translate
- // DefaultIdPolicy to ProjectionPolicies::DefaultIdPolicy in order to avoid exposing
- // internal aggregation types to the query system.
- const auto idPolicy = (defaultIdPolicy == ProjectionExecAgg::DefaultIdPolicy::kIncludeId
- ? ProjectionPolicies::DefaultIdPolicy::kIncludeId
- : ProjectionPolicies::DefaultIdPolicy::kExcludeId);
-
- // By default, $project will recurse through nested arrays. If the caller has specified that
- // it should not, we inhibit it from doing so here. We separate this class' internal enum
- // ArrayRecursionPolicy from ProjectionPolicies::ArrayRecursionPolicy in order to avoid
- // exposing aggregation types to the query system.
- const auto recursionPolicy =
- (arrayRecursionPolicy == ArrayRecursionPolicy::kRecurseNestedArrays
- ? ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays
- : ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
-
- // Inclusion projections permit computed fields by default, so we must explicitly ban them.
- // Computed fields are implicitly banned for exclusions.
- const auto computedFieldsPolicy =
- ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields;
-
- // Create a ProjectionPolicies object, to be populated based on the passed arguments.
- ProjectionPolicies projectionPolicies{idPolicy, recursionPolicy, computedFieldsPolicy};
-
- // Construct a ParsedAggregationProjection for the given projection spec and policies.
- const auto proj = projection_ast::parse(expCtx, projSpec, projectionPolicies);
- _projection = projection_executor::buildProjectionExecutor(
- expCtx, &proj, projectionPolicies, true /* optimizeExecutor */);
-
- // For an inclusion, record the exhaustive set of fields retained by the projection.
- if (getType() == ProjectionType::kInclusionProjection) {
- DepsTracker depsTracker;
- _projection->addDependencies(&depsTracker);
- for (auto&& field : depsTracker.fields)
- _exhaustivePaths.insert(FieldRef{field});
- }
- }
-
- const std::set<FieldRef>& getExhaustivePaths() const {
- return _exhaustivePaths;
- }
-
- ProjectionType getType() const {
- return (_projection->getType() == TransformerType::kInclusionProjection
- ? ProjectionType::kInclusionProjection
- : ProjectionType::kExclusionProjection);
- }
-
- BSONObj applyProjection(BSONObj inputDoc) const {
- return applyTransformation(Document{inputDoc}).toBson();
- }
-
- bool applyProjectionToOneField(StringData field) const {
- MutableDocument doc;
- const FieldPath f{field};
- doc.setNestedField(f, Value(1.0));
- const Document transformedDoc = applyTransformation(doc.freeze());
- return !transformedDoc.getNestedField(f).missing();
- }
-
- stdx::unordered_set<std::string> applyProjectionToFields(
- const stdx::unordered_set<std::string>& fields) const {
- stdx::unordered_set<std::string> out;
-
- for (const auto& field : fields) {
- if (applyProjectionToOneField(field)) {
- out.insert(field);
- }
- }
-
- return out;
- }
-
-private:
- Document applyTransformation(Document inputDoc) const {
- return _projection->applyTransformation(inputDoc);
- }
-
- std::unique_ptr<ParsedAggregationProjection> _projection;
- std::set<FieldRef> _exhaustivePaths;
-};
-
-// ProjectionExecAgg's constructor and destructor are defined here, at a point where the
-// implementation of ProjectionExecutor is known, so that std::unique_ptr can be used with the
-// forward-declared ProjectionExecutor class.
-ProjectionExecAgg::ProjectionExecAgg(BSONObj projSpec, std::unique_ptr<ProjectionExecutor> exec)
- : _exec(std::move(exec)), _projSpec(std::move(projSpec)){};
-
-ProjectionExecAgg::~ProjectionExecAgg() = default;
-
-std::unique_ptr<ProjectionExecAgg> ProjectionExecAgg::create(BSONObj projSpec,
- DefaultIdPolicy defaultIdPolicy,
- ArrayRecursionPolicy recursionPolicy) {
- return std::unique_ptr<ProjectionExecAgg>(new ProjectionExecAgg(
- projSpec,
- std::make_unique<ProjectionExecutor>(projSpec, defaultIdPolicy, recursionPolicy)));
-}
-
-ProjectionExecAgg::ProjectionType ProjectionExecAgg::getType() const {
- return _exec->getType();
-}
-
-BSONObj ProjectionExecAgg::applyProjection(BSONObj inputDoc) const {
- return _exec->applyProjection(inputDoc);
-}
-
-bool ProjectionExecAgg::applyProjectionToOneField(StringData field) const {
- return _exec->applyProjectionToOneField(field);
-}
-
-stdx::unordered_set<std::string> ProjectionExecAgg::applyProjectionToFields(
- const stdx::unordered_set<std::string>& fields) const {
- return _exec->applyProjectionToFields(fields);
-}
-
-const std::set<FieldRef>& ProjectionExecAgg::getExhaustivePaths() const {
- return _exec->getExhaustivePaths();
-}
-} // namespace mongo
diff --git a/src/mongo/db/exec/projection_exec_agg.h b/src/mongo/db/exec/projection_exec_agg.h
deleted file mode 100644
index f15c71f7c40..00000000000
--- a/src/mongo/db/exec/projection_exec_agg.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Copyright (C) 2018-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.
- */
-
-#pragma once
-
-#include <memory>
-
-#include "mongo/bson/bsonobj.h"
-#include "mongo/db/field_ref.h"
-
-namespace mongo {
-
-/**
- * This class provides the query system with the ability to perform projections using the
- * aggregation system's projection semantics.
- */
-class ProjectionExecAgg {
-public:
- // Allows the caller to indicate whether the projection should default to including or excluding
- // the _id field in the event that the projection spec does not specify the desired behavior.
- // For instance, given a projection {a: 1}, specifying 'kExcludeId' is equivalent to projecting
- // {a: 1, _id: 0} while 'kIncludeId' is equivalent to the projection {a: 1, _id: 1}. If the user
- // explicitly specifies a projection on _id, then this will override the default policy; for
- // instance, {a: 1, _id: 0} will exclude _id for both 'kExcludeId' and 'kIncludeId'.
- enum class DefaultIdPolicy { kIncludeId, kExcludeId };
-
- // Allows the caller to specify how the projection should handle nested arrays; that is, an
- // array whose immediate parent is itself an array. For example, in the case of sample document
- // {a: [1, 2, [3, 4], {b: [5, 6]}]} the array [3, 4] is a nested array. The array [5, 6] is not,
- // because there is an intervening object between it and its closest array ancestor.
- enum class ArrayRecursionPolicy { kRecurseNestedArrays, kDoNotRecurseNestedArrays };
-
- enum class ProjectionType { kInclusionProjection, kExclusionProjection };
-
- static std::unique_ptr<ProjectionExecAgg> create(BSONObj projSpec,
- DefaultIdPolicy defaultIdPolicy,
- ArrayRecursionPolicy recursionPolicy);
-
- ~ProjectionExecAgg();
-
- BSONObj applyProjection(BSONObj inputDoc) const;
-
- stdx::unordered_set<std::string> applyProjectionToFields(
- const stdx::unordered_set<std::string>& fields) const;
-
- /**
- * Apply the projection to a single field name. Returns whether or not the projection would
- * allow that field to remain in a document.
- **/
- bool applyProjectionToOneField(StringData field) const;
-
- /**
- * Returns the exhaustive set of all paths that will be preserved by this projection, or an
- * empty set if the exhaustive set cannot be determined. An inclusion will always produce an
- * exhaustive set; an exclusion will always produce an empty set.
- */
- const std::set<FieldRef>& getExhaustivePaths() const;
-
- ProjectionType getType() const;
-
- BSONObj getProjectionSpec() const {
- return _projSpec;
- }
-
-private:
- /**
- * ProjectionExecAgg::ProjectionExecutor wraps all agg-specific calls, and is forward-declared
- * here to avoid exposing any types from ParsedAggregationProjection to the query system.
- */
- class ProjectionExecutor;
-
- ProjectionExecAgg(BSONObj projSpec, std::unique_ptr<ProjectionExecutor> exec);
-
- std::unique_ptr<ProjectionExecutor> _exec;
- const BSONObj _projSpec;
-};
-} // namespace mongo
diff --git a/src/mongo/db/exec/projection_executor.h b/src/mongo/db/exec/projection_executor.h
index 3045e4ca710..7921d4510e8 100644
--- a/src/mongo/db/exec/projection_executor.h
+++ b/src/mongo/db/exec/projection_executor.h
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2019-present MongoDB, Inc.
+ * Copyright (C) 2018-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,
@@ -29,20 +29,103 @@
#pragma once
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/query/projection_ast.h"
-#include "mongo/db/query/projection_ast_walker.h"
+#include "mongo/platform/basic.h"
+
+#include <boost/intrusive_ptr.hpp>
+#include <memory>
+
+#include "mongo/bson/bsonelement.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/pipeline/field_path.h"
+#include "mongo/db/pipeline/transformer_interface.h"
+#include "mongo/db/query/projection_policies.h"
namespace mongo::projection_executor {
/**
- * Builds a projection execution tree from the given 'projection' and using the given projection
- * 'policies' by walking an AST tree starting at the root node stored within the 'projection'.
- * Set 'optimizeExecutor' to 'true' when the 'optimize()' method needs to be called on the newly
- * created executor before returning it to the caller.
+ * A ProjectionExecutor is responsible for parsing and executing a $project. It represents either an
+ * inclusion or exclusion projection. This is the common interface between the two types of
+ * projections.
*/
-std::unique_ptr<parsed_aggregation_projection::ParsedAggregationProjection> buildProjectionExecutor(
- boost::intrusive_ptr<ExpressionContext> expCtx,
- const projection_ast::Projection* projection,
- ProjectionPolicies policies,
- bool optimizeExecutor);
+class ProjectionExecutor : public TransformerInterface {
+public:
+ /**
+ * The name of an internal variable to bind a projection post image to, which is used by the
+ * '_rootReplacementExpression' to replace the content of the transformed document.
+ */
+ static constexpr StringData kProjectionPostImageVarName{"INTERNAL_PROJ_POST_IMAGE"_sd};
+
+ /**
+ * Optimize any expressions contained within this projection.
+ */
+ void optimize() override {
+ if (_rootReplacementExpression) {
+ _rootReplacementExpression->optimize();
+ }
+ }
+
+ /**
+ * Add any dependencies needed by this projection or any sub-expressions to 'deps'.
+ */
+ DepsTracker::State addDependencies(DepsTracker* deps) const override {
+ return DepsTracker::State::NOT_SUPPORTED;
+ }
+
+ /**
+ * Apply the projection transformation.
+ */
+ Document applyTransformation(const Document& input) override {
+ auto output = applyProjection(input);
+ if (_rootReplacementExpression) {
+ return _applyRootReplacementExpression(input, output);
+ }
+ return output;
+ }
+
+ /**
+ * Sets 'expr' as a root-replacement expression to this tree. A root-replacement expression,
+ * once evaluated, will replace an entire output document. A projection post image document
+ * will be accessible via the special variable, whose name is stored in
+ * 'kProjectionPostImageVarName', if this expression needs access to it.
+ */
+ void setRootReplacementExpression(boost::intrusive_ptr<Expression> expr) {
+ _rootReplacementExpression = expr;
+ }
+
+protected:
+ ProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies)
+ : _expCtx(expCtx),
+ _policies(policies),
+ _projectionPostImageVarId{
+ _expCtx->variablesParseState.defineVariable(kProjectionPostImageVarName)} {}
+
+ /**
+ * Apply the projection to 'input'.
+ */
+ virtual Document applyProjection(const Document& input) const = 0;
+
+ boost::intrusive_ptr<ExpressionContext> _expCtx;
+
+ ProjectionPolicies _policies;
+
+ boost::intrusive_ptr<Expression> _rootReplacementExpression;
+
+private:
+ Document _applyRootReplacementExpression(const Document& input, const Document& output) {
+ using namespace fmt::literals;
+
+ _expCtx->variables.setValue(_projectionPostImageVarId, Value{output});
+ auto val = _rootReplacementExpression->evaluate(input, &_expCtx->variables);
+ uassert(51254,
+ "Root-replacement expression must return a document, but got {}"_format(
+ typeName(val.getType())),
+ val.getType() == BSONType::Object);
+ return val.getDocument();
+ }
+
+ // This variable id is used to bind a projection post-image so that it can be accessed by
+ // root-replacement expressions which apply projection to the entire post-image document, rather
+ // than to a specific field.
+ Variables::Id _projectionPostImageVarId;
+};
} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/projection_executor.cpp b/src/mongo/db/exec/projection_executor_builder.cpp
index cecc5a468d4..9bb5eb46cd9 100644
--- a/src/mongo/db/exec/projection_executor.cpp
+++ b/src/mongo/db/exec/projection_executor_builder.cpp
@@ -30,23 +30,19 @@
#include "mongo/platform/basic.h"
#include "mongo/base/exact_cast.h"
+#include "mongo/db/exec/exclusion_projection_executor.h"
+#include "mongo/db/exec/inclusion_projection_executor.h"
#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/pipeline/expression_find_internal.h"
-#include "mongo/db/pipeline/parsed_exclusion_projection.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
#include "mongo/db/query/projection_ast_path_tracking_visitor.h"
+#include "mongo/db/query/projection_ast_walker.h"
#include "mongo/db/query/util/make_data_structure.h"
namespace mongo::projection_executor {
namespace {
-using ParsedAggregationProjection = parsed_aggregation_projection::ParsedAggregationProjection;
-using ParsedInclusionProjection = parsed_aggregation_projection::ParsedInclusionProjection;
-using ParsedExclusionProjection = parsed_aggregation_projection::ParsedExclusionProjection;
-
constexpr auto kInclusion = projection_ast::ProjectType::kInclusion;
constexpr auto kExclusion = projection_ast::ProjectType::kExclusion;
-constexpr auto kProjectionPostImageVarName =
- parsed_aggregation_projection::ParsedAggregationProjection::kProjectionPostImageVarName;
+constexpr auto kProjectionPostImageVarName = ProjectionExecutor::kProjectionPostImageVarName;
/**
* Holds data used to built a projection executor while walking an AST tree. This struct is attached
@@ -174,7 +170,7 @@ public:
}
void visit(const projection_ast::ProjectionPositionalASTNode* node) final {
- constexpr auto isInclusion = std::is_same_v<Executor, ParsedInclusionProjection>;
+ constexpr auto isInclusion = std::is_same_v<Executor, InclusionProjectionExecutor>;
invariant(isInclusion);
const auto& path = _context->fullPath();
@@ -191,7 +187,7 @@ public:
// A $slice expression can be applied to an exclusion projection. In this case we don't need
// to project out the path to which $slice is applied, since it will already be included
// into the output document.
- if constexpr (std::is_same_v<Executor, ParsedInclusionProjection>) {
+ if constexpr (std::is_same_v<Executor, InclusionProjectionExecutor>) {
userData.rootNode()->addProjectionForPath(path.fullPath());
}
@@ -219,7 +215,7 @@ public:
// In an inclusion projection only the _id field can be excluded from the result document.
// If this is the case, then we don't need to include the field into the projection.
- if constexpr (std::is_same_v<Executor, ParsedInclusionProjection>) {
+ if constexpr (std::is_same_v<Executor, InclusionProjectionExecutor>) {
const auto isIdField = path == "_id";
if (isIdField && !node->value()) {
return;
@@ -260,7 +256,7 @@ auto buildProjectionExecutor(boost::intrusive_ptr<ExpressionContext> expCtx,
}
} // namespace
-std::unique_ptr<ParsedAggregationProjection> buildProjectionExecutor(
+std::unique_ptr<ProjectionExecutor> buildProjectionExecutor(
boost::intrusive_ptr<ExpressionContext> expCtx,
const projection_ast::Projection* projection,
const ProjectionPolicies policies,
@@ -269,10 +265,10 @@ std::unique_ptr<ParsedAggregationProjection> buildProjectionExecutor(
switch (projection->type()) {
case kInclusion:
- return buildProjectionExecutor<ParsedInclusionProjection>(
+ return buildProjectionExecutor<InclusionProjectionExecutor>(
expCtx, projection->root(), policies, optimizeExecutor);
case kExclusion:
- return buildProjectionExecutor<ParsedExclusionProjection>(
+ return buildProjectionExecutor<ExclusionProjectionExecutor>(
expCtx, projection->root(), policies, optimizeExecutor);
default:
MONGO_UNREACHABLE;
diff --git a/src/mongo/db/exec/projection_executor_builder.h b/src/mongo/db/exec/projection_executor_builder.h
new file mode 100644
index 00000000000..9d934894c17
--- /dev/null
+++ b/src/mongo/db/exec/projection_executor_builder.h
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2019-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.
+ */
+
+#pragma once
+
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/query/projection_ast.h"
+#include "mongo/db/query/projection_ast_walker.h"
+
+namespace mongo::projection_executor {
+/**
+ * Builds a projection execution tree from the given 'projection' and using the given projection
+ * 'policies' by walking an AST tree starting at the root node stored within the 'projection'.
+ * Set 'optimizeExecutor' to 'true' when the 'optimize()' method needs to be called on the newly
+ * created executor before returning it to the caller.
+ */
+std::unique_ptr<ProjectionExecutor> buildProjectionExecutor(
+ boost::intrusive_ptr<ExpressionContext> expCtx,
+ const projection_ast::Projection* projection,
+ ProjectionPolicies policies,
+ bool optimizeExecutor);
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/projection_executor_builder_test.cpp b/src/mongo/db/exec/projection_executor_builder_test.cpp
new file mode 100644
index 00000000000..f12e1353743
--- /dev/null
+++ b/src/mongo/db/exec/projection_executor_builder_test.cpp
@@ -0,0 +1,241 @@
+/**
+ * Copyright (C) 2019-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/document_value/document_value_test_util.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.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 "mongo/db/query/projection_parser.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo::projection_executor {
+namespace {
+constexpr auto kOptimzeExecutor = true;
+
+class ProjectionExecutorTest : public AggregationContextFixture {
+public:
+ projection_ast::Projection parseWithDefaultPolicies(
+ const BSONObj& projectionBson, boost::optional<BSONObj> matchExprBson = boost::none) {
+ return parseWithPolicies(projectionBson, matchExprBson, ProjectionPolicies{});
+ }
+
+ projection_ast::Projection parseWithFindFeaturesEnabled(
+ const BSONObj& projectionBson, boost::optional<BSONObj> matchExprBson = boost::none) {
+ auto policy = ProjectionPolicies::findProjectionPolicies();
+ return parseWithPolicies(projectionBson, matchExprBson, policy);
+ }
+
+ 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);
+ }
+};
+
+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'}")}));
+
+ 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'}")}));
+}
+
+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_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_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_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_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_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_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]}}")}));
+
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: {b: [4]}}")},
+ executor->applyTransformation(Document{fromjson("{a: {b: [4, 3, 2]}}")}));
+}
+
+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_F(ProjectionExecutorTest, CanProjectFindElemMatch) {
+
+ 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}));
+ }
+
+ {
+ 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}));
+ }
+}
+
+TEST_F(ProjectionExecutorTest, ElemMatchRespectsCollator) {
+ CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
+ getExpCtx()->setCollator(&collator);
+
+ auto proj = parseWithFindFeaturesEnabled(fromjson("{a: {$elemMatch: {$gte: 'abc'}}}"));
+ auto executor = buildProjectionExecutor(getExpCtx(), &proj, {}, kOptimzeExecutor);
+
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{ a: [ \"zdd\" ] }")},
+ executor->applyTransformation(Document{fromjson("{a: ['zaa', 'zbb', 'zdd', 'zee']}")}));
+}
+
+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_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_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_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_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_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_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));
+}
+} // namespace
+} // namespace mongo::projection_executor
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
diff --git a/src/mongo/db/exec/find_projection_executor.cpp b/src/mongo/db/exec/projection_executor_utils.cpp
index 9ff1bcd4602..0923e2e2fab 100644
--- a/src/mongo/db/exec/find_projection_executor.cpp
+++ b/src/mongo/db/exec/projection_executor_utils.cpp
@@ -27,12 +27,47 @@
* it in the license file.
*/
-#include "mongo/platform/basic.h"
+#include "mongo/db/exec/projection_executor.h"
-#include "mongo/db/exec/find_projection_executor.h"
+namespace mongo::projection_executor_utils {
+bool applyProjectionToOneField(projection_executor::ProjectionExecutor* executor,
+ StringData field) {
+ const FieldPath fp{field};
+ MutableDocument md;
+ md.setNestedField(fp, Value{1.0});
+ auto output = executor->applyTransformation(md.freeze());
+ return !output.getNestedField(fp).missing();
+ return false;
+}
+
+stdx::unordered_set<std::string> applyProjectionToFields(
+ projection_executor::ProjectionExecutor* executor,
+ const stdx::unordered_set<std::string>& fields) {
+ stdx::unordered_set<std::string> out;
+
+ for (const auto& field : fields) {
+ if (applyProjectionToOneField(executor, field)) {
+ out.insert(field);
+ }
+ }
+
+ return out;
+}
+
+std::set<FieldRef> extractExhaustivePaths(const projection_executor::ProjectionExecutor* executor) {
+ std::set<FieldRef> exhaustivePaths;
+
+ if (executor->getType() == TransformerInterface::TransformerType::kInclusionProjection) {
+ DepsTracker depsTracker;
+ executor->addDependencies(&depsTracker);
+ for (auto&& field : depsTracker.fields) {
+ exhaustivePaths.insert(FieldRef{field});
+ }
+ }
+
+ return exhaustivePaths;
+}
-namespace mongo {
-namespace projection_executor {
namespace {
/**
* Holds various parameters required to apply a $slice projection. Populated from the arguments
@@ -44,9 +79,21 @@ struct SliceParams {
const int limit;
};
-Value applySliceProjectionHelper(const Document& input,
- const SliceParams& params,
- size_t fieldPathIndex);
+/**
+ * Extracts an element from the array 'arr' at position 'elemIndex'. The 'elemIndex' string
+ * parameter must hold a value which can be converted to an unsigned integer. If 'elemIndex' is not
+ * within array boundaries, an empty Value is returned.
+ */
+Value extractArrayElement(const Value& arr, const std::string& elemIndex) {
+ auto index = str::parseUnsignedBase10Integer(elemIndex);
+ invariant(index);
+ return arr[*index];
+}
+
+
+Value applyFindSliceProjectionHelper(const Document& input,
+ const SliceParams& params,
+ size_t fieldPathIndex);
/**
* Returns a portion of the 'array', skipping a number of elements as indicated by the 'skip'
@@ -95,16 +142,16 @@ Value sliceArray(const std::vector<Value>& array, boost::optional<int> skip, int
* on the path "a.b", but {a: [{b: 1}]} does, so nested arrays are stored within the output array
* as regular values.
*/
-Value applySliceProjectionToArray(const std::vector<Value>& array,
- const SliceParams& params,
- size_t fieldPathIndex) {
+Value applyFindSliceProjectionToArray(const std::vector<Value>& array,
+ const SliceParams& params,
+ size_t fieldPathIndex) {
std::vector<Value> output;
output.reserve(array.size());
for (const auto& elem : array) {
output.push_back(
elem.getType() == BSONType::Object
- ? applySliceProjectionHelper(elem.getDocument(), params, fieldPathIndex)
+ ? applyFindSliceProjectionHelper(elem.getDocument(), params, fieldPathIndex)
: elem);
}
@@ -123,9 +170,9 @@ Value applySliceProjectionToArray(const std::vector<Value>& array,
* the result in 'val'.
* * Store the computed 'val' in the 'output' document under the current field name.
*/
-Value applySliceProjectionHelper(const Document& input,
- const SliceParams& params,
- size_t fieldPathIndex) {
+Value applyFindSliceProjectionHelper(const Document& input,
+ const SliceParams& params,
+ size_t fieldPathIndex) {
invariant(fieldPathIndex < params.path.getPathLength());
auto fieldName = params.path.getFieldName(fieldPathIndex++);
@@ -135,11 +182,11 @@ Value applySliceProjectionHelper(const Document& input,
case BSONType::Array:
val = (fieldPathIndex == params.path.getPathLength())
? sliceArray(val.getArray(), params.skip, params.limit)
- : applySliceProjectionToArray(val.getArray(), params, fieldPathIndex);
+ : applyFindSliceProjectionToArray(val.getArray(), params, fieldPathIndex);
break;
case BSONType::Object:
if (fieldPathIndex < params.path.getPathLength()) {
- val = applySliceProjectionHelper(val.getDocument(), params, fieldPathIndex);
+ val = applyFindSliceProjectionHelper(val.getDocument(), params, fieldPathIndex);
}
break;
default:
@@ -152,16 +199,10 @@ Value applySliceProjectionHelper(const Document& input,
}
} // namespace
-Value extractArrayElement(const Value& arr, const std::string& elemIndex) {
- auto index = str::parseUnsignedBase10Integer(elemIndex);
- invariant(index);
- return arr[*index];
-}
-
-Document applyPositionalProjection(const Document& preImage,
- const Document& postImage,
- const MatchExpression& matchExpr,
- const FieldPath& path) {
+Document applyFindPositionalProjection(const Document& preImage,
+ const Document& postImage,
+ const MatchExpression& matchExpr,
+ const FieldPath& path) {
MutableDocument output(postImage);
// Try to find the first matching array element from the 'input' document based on the condition
@@ -217,9 +258,9 @@ Document applyPositionalProjection(const Document& preImage,
return output.freeze();
}
-Value applyElemMatchProjection(const Document& input,
- const MatchExpression& matchExpr,
- const FieldPath& path) {
+Value applyFindElemMatchProjection(const Document& input,
+ const MatchExpression& matchExpr,
+ const FieldPath& path) {
invariant(path.getPathLength() == 1);
// Try to find the first matching array element from the 'input' document based on the condition
@@ -241,14 +282,13 @@ Value applyElemMatchProjection(const Document& input,
return Value{std::vector<Value>{matchingElem}};
}
-Document applySliceProjection(const Document& input,
- const FieldPath& path,
- boost::optional<int> skip,
- int limit) {
+Document applyFindSliceProjection(const Document& input,
+ const FieldPath& path,
+ boost::optional<int> skip,
+ int limit) {
auto params = SliceParams{path, skip, limit};
- auto val = applySliceProjectionHelper(input, params, 0);
+ auto val = applyFindSliceProjectionHelper(input, params, 0);
invariant(val.getType() == BSONType::Object);
return val.getDocument();
}
-} // namespace projection_executor
-} // namespace mongo
+} // namespace mongo::projection_executor_utils
diff --git a/src/mongo/db/exec/find_projection_executor.h b/src/mongo/db/exec/projection_executor_utils.h
index 695da74cbec..338d12d39d3 100644
--- a/src/mongo/db/exec/find_projection_executor.h
+++ b/src/mongo/db/exec/projection_executor_utils.h
@@ -30,17 +30,31 @@
#pragma once
#include "mongo/db/exec/document_value/document.h"
+#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/pipeline/field_path.h"
-namespace mongo {
-namespace projection_executor {
+namespace mongo::projection_executor_utils {
/**
- * Extracts an element from the array 'arr' at position 'elemIndex'. The 'elemIndex' string
- * parameter must hold a value which can be converted to an unsigned integer. If 'elemIndex' is not
- * within array boundaries, an empty Value is returned.
+ * Applies the projection to a single field name. Returns whether or not the projection would
+ * allow that field to remain in a document.
+ **/
+bool applyProjectionToOneField(projection_executor::ProjectionExecutor* executor, StringData field);
+
+/**
+ * Applies the projection to each field from the 'fields' set and stores it in the returned set
+ * if the projection would allow that field to remain in a document.
+ **/
+stdx::unordered_set<std::string> applyProjectionToFields(
+ projection_executor::ProjectionExecutor* executor,
+ const stdx::unordered_set<std::string>& fields);
+
+/**
+ * Returns the exhaustive set of all paths that will be preserved by this projection, or an
+ * empty set if the exhaustive set cannot be determined. An inclusion will always produce an
+ * exhaustive set; an exclusion will always produce an empty set.
*/
-Value extractArrayElement(const Value& arr, const std::string& elemIndex);
+std::set<FieldRef> extractExhaustivePaths(const projection_executor::ProjectionExecutor* executor);
/**
* Applies a positional projection on the first array found in the 'path' on a projection
@@ -63,10 +77,10 @@ Value extractArrayElement(const Value& arr, const std::string& elemIndex);
* Throws an AssertionException if 'matchExpr' matches the input document, but an array element
* satisfying positional projection requirements cannot be found.
*/
-Document applyPositionalProjection(const Document& preImage,
- const Document& postImage,
- const MatchExpression& matchExpr,
- const FieldPath& path);
+Document applyFindPositionalProjection(const Document& preImage,
+ const Document& postImage,
+ const MatchExpression& matchExpr,
+ const FieldPath& path);
/**
* Applies an $elemMatch projection on the array at the given 'path' on the 'input' document. The
* applied projection is stored in the output Value. The 'matchExpr' specifies a condition to
@@ -85,9 +99,9 @@ Document applyPositionalProjection(const Document& preImage,
* Since the $elemMatch projection cannot be used with a nested field, the 'path' value must not
* be a dotted path, otherwise an invariant will be triggered.
*/
-Value applyElemMatchProjection(const Document& input,
- const MatchExpression& matchExpr,
- const FieldPath& path);
+Value applyFindElemMatchProjection(const Document& input,
+ const MatchExpression& matchExpr,
+ const FieldPath& path);
/**
* Applies a $slice projection on the array at the given 'path' on the 'input' document. The applied
* projection is returned as a Document. The 'skip' parameter indicates the number of items in the
@@ -107,10 +121,8 @@ Value applyElemMatchProjection(const Document& input,
*
* The resulting document will contain the following element: {foo: [{bar: 2}, {bar: 3}]}.
*/
-Document applySliceProjection(const Document& input,
- const FieldPath& path,
- boost::optional<int> skip,
- int limit);
-
-} // namespace projection_executor
-} // namespace mongo
+Document applyFindSliceProjection(const Document& input,
+ const FieldPath& path,
+ boost::optional<int> skip,
+ int limit);
+} // namespace mongo::projection_executor_utils
diff --git a/src/mongo/db/exec/projection_executor_utils_test.cpp b/src/mongo/db/exec/projection_executor_utils_test.cpp
new file mode 100644
index 00000000000..867a1de7704
--- /dev/null
+++ b/src/mongo/db/exec/projection_executor_utils_test.cpp
@@ -0,0 +1,336 @@
+/**
+ * Copyright (C) 2019-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/document_value/document_value_test_util.h"
+#include "mongo/db/exec/projection_executor_utils.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo::projection_executor_utils {
+namespace positional_projection_tests {
+/**
+ * Applies a find()-style positional projection at the given 'path' using 'matchSpec' to create
+ * a 'MatchExpression' to match an element on the first array in the 'path'. If no value for
+ * 'postImage' is provided, then the post-image used will be the value passed for the 'preImage'.
+ */
+auto applyPositional(const BSONObj& matchSpec,
+ const std::string& path,
+ const Document& preImage,
+ boost::optional<Document> postImage = boost::none) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, expCtx));
+ return projection_executor_utils::applyFindPositionalProjection(
+ preImage, postImage.value_or(preImage), *matchExpr, path);
+}
+
+TEST(PositionalProjection, CorrectlyProjectsSimplePath) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: 1, foo: [6]}")},
+ applyPositional(fromjson("{bar: 1, foo: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
+}
+
+TEST(PositionalProjection, CorrectlyProjectsDottedPath) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1, x: {y: [6]}}")},
+ applyPositional(fromjson("{a: 1, 'x.y': {$gte: 5}}"),
+ "x.y",
+ Document{fromjson("{a: 1, x: {y: [1,2,6,10]}}")}));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: 1, x: {y: {z: [6]}}}")},
+ applyPositional(fromjson("{a: 1, 'x.y.z': {$gte: 5}}"),
+ "x.y.z",
+ Document{fromjson("{a: 1, x: {y: {z: [1,2,6,10]}}}")}));
+}
+
+TEST(PositionalProjection, ProjectsValueUnmodifiedIfFieldIsNotArray) {
+ auto doc = Document{fromjson("{foo: 3}")};
+ ASSERT_DOCUMENT_EQ(doc, applyPositional(fromjson("{foo: 3}"), "foo", doc));
+}
+
+TEST(PositionalProjection, FailsToProjectPositionalPathComponentsForNestedArrays) {
+ ASSERT_THROWS_CODE(applyPositional(fromjson("{'x.0.y': 42}"),
+ "x.0.y",
+ Document{fromjson("{x: [{y: [11, 42]}]}")}),
+ AssertionException,
+ 51247);
+}
+
+TEST(PositionalProjection, CorrectlyProjectsNestedArrays) {
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [1,2]}]}")},
+ applyPositional(fromjson("{'a.b': 1}"),
+ "a",
+ Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3,4]}]}")},
+ applyPositional(fromjson("{'a.b': 3}"),
+ "a",
+ Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: [3,4]}]}")},
+ applyPositional(fromjson("{'a.b': 3}"),
+ "a.b",
+ Document{fromjson("{a: [{b: [1,2]}, {b: [3,4]}]}")}));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [['d','e','f']]}")},
+ applyPositional(fromjson("{a: {$gt: ['a','b','c']}}"),
+ "a",
+ Document{fromjson("{a: [['a','b','c'],['d','e','f']]}")}));
+}
+
+TEST(PositionalProjection, FailsToProjectWithMultipleConditionsOnArray) {
+ ASSERT_THROWS_CODE(applyPositional(fromjson("{$or: [{'x.y': 1}, {'x.y': 2}]}"),
+ "x",
+ Document{fromjson("{x: [{y: [1,2]}]}")}),
+ AssertionException,
+ 51246);
+}
+
+TEST(PositionalProjection, CanMergeWithExistingFieldsInOutputDocument) {
+ auto doc = Document{fromjson("{foo: {bar: [1,2,6,10]}}")};
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{foo: {bar: [6]}}")},
+ applyPositional(fromjson("{'foo.bar': {$gte: 5}}"), "foo.bar", doc));
+
+ doc = Document{fromjson("{bar: 1, foo: {bar: [1,2,6,10]}}")};
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: 1, foo: {bar: [6]}}")},
+ applyPositional(fromjson("{bar: 1, 'foo.bar': {$gte: 5}}"), "foo.bar", doc));
+
+ doc = Document{fromjson("{bar: 1, foo: 3}")};
+ ASSERT_DOCUMENT_EQ(doc, applyPositional(fromjson("{foo: 3}"), "foo", doc));
+}
+
+TEST(PositionalProjection, AppliesMatchExpressionToPreImageAndStoresResultInPostImage) {
+ auto preImage = Document{fromjson("{foo: 1, bar: [1,2,6,10]}")};
+ auto postImage = Document{fromjson("{bar: [1,2,6,10]}")};
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{bar: [6]}")},
+ applyPositional(fromjson("{foo: 1, bar: 6}"), "bar", preImage, postImage));
+}
+} // namespace positional_projection_tests
+
+namespace elem_match_projection_tests {
+auto applyElemMatch(const BSONObj& match, const std::string& path, const Document& input) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto matchObj = BSON(path << BSON("$elemMatch" << match));
+ auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchObj, expCtx));
+ return projection_executor_utils::applyFindElemMatchProjection(input, *matchExpr, path);
+}
+
+TEST(ElemMatchProjection, CorrectlyProjectsNonObjectElement) {
+ ASSERT_VALUE_EQ(
+ Document{fromjson("{foo: [4]}")}["foo"],
+ applyElemMatch(fromjson("{$in: [4]}"), "foo", Document{fromjson("{foo: [1,2,3,4]}")}));
+ ASSERT_VALUE_EQ(
+ Document{fromjson("{foo: [4]}")}["foo"],
+ applyElemMatch(fromjson("{$nin: [1,2,3]}"), "foo", Document{fromjson("{foo: [1,2,3,4]}")}));
+}
+
+TEST(ElemMatchProjection, CorrectlyProjectsObjectElement) {
+ ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
+ applyElemMatch(fromjson("{bar: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
+ "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+}
+
+TEST(ElemMatchProjection, CorrectlyProjectsArrayElement) {
+ ASSERT_VALUE_EQ(Document{fromjson("{foo: [[3,4]]}")}["foo"],
+ applyElemMatch(fromjson("{$gt: [1,2]}"),
+ "foo",
+ Document{fromjson("{foo: [[1,2], [3,4]]}")}));
+}
+
+TEST(ElemMatchProjection, ProjectsAsEmptyDocumentIfInputIsEmpty) {
+ ASSERT_VALUE_EQ({}, applyElemMatch(fromjson("{bar: {$gte: 5}}"), "foo", {}));
+}
+
+TEST(ElemMatchProjection, RemovesFieldFromOutputDocumentIfUnableToMatchArrayElement) {
+ ASSERT_VALUE_EQ({},
+ applyElemMatch(fromjson("{bar: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{foo: [{bar: 1, z: 1}, "
+ "{bar: 2, z: 2}]}")}));
+ ASSERT_VALUE_EQ(
+ {},
+ applyElemMatch(fromjson("{bar: {$gte: 20}}"),
+ "foo",
+ Document{fromjson("{bar: 1, foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
+ "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+}
+
+TEST(ElemMatchProjection, CorrectlyProjectsWithMultipleCriteriaInMatchExpression) {
+ ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 2, z: 2}]}")}["foo"],
+ applyElemMatch(fromjson("{bar: {$gt: 1, $lt: 6}}"),
+ "foo",
+ Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
+ "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+}
+
+TEST(ElemMatchProjection, CanMergeWithExistingFieldsInInputDocument) {
+ ASSERT_VALUE_EQ(Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
+ applyElemMatch(fromjson("{bar: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
+ "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+
+ ASSERT_VALUE_EQ(
+ Document{fromjson("{foo: [{bar: 6, z: 6}]}")}["foo"],
+ applyElemMatch(fromjson("{bar: {$gte: 5}}"),
+ "foo",
+ Document{fromjson("{bar: 1, foo: [{bar: 1, z: 1}, {bar: 2, z: 2}, "
+ "{bar: 6, z: 6}, {bar: 10, z: 10}]}")}));
+}
+
+TEST(ElemMatchProjection, RertursEmptyValuefItContainsNumericSubfield) {
+ ASSERT_VALUE_EQ(
+ {}, applyElemMatch(fromjson("{$gt: 2}"), "foo", Document{BSON("foo" << BSON(0 << 3))}));
+
+ ASSERT_VALUE_EQ({},
+ applyElemMatch(fromjson("{$gt: 2}"),
+ "foo",
+ Document{BSON("bar" << 1 << "foo" << BSON(0 << 3))}));
+}
+} // namespace elem_match_projection_tests
+
+namespace slice_projection_tests {
+DEATH_TEST(SliceProjection,
+ ShouldFailIfNegativeLimitSpecifiedWithPositiveSkip,
+ "Invariant failure limit >= 0") {
+ auto doc = Document{fromjson("{a: [1,2,3,4]}")};
+ projection_executor_utils::applyFindSliceProjection(doc, "a", 1, -1);
+}
+
+DEATH_TEST(SliceProjection,
+ ShouldFailIfNegativeLimitSpecifiedWithNegativeSkip,
+ "Invariant failure limit >= 0") {
+ auto doc = Document{fromjson("{a: [1,2,3,4]}")};
+ projection_executor_utils::applyFindSliceProjection(doc, "a", -1, -1);
+}
+
+TEST(SliceProjection, CorrectlyProjectsSimplePath) {
+ auto doc = Document{fromjson("{a: [1,2,3,4]}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [1,2,3]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, 3));
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, -3));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [2]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", -3, 1));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", -3, 4));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [4]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", 3, 1));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", -5, 5));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: []}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", 5, 2));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", -5, 2));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(
+ doc, "a", boost::none, std::numeric_limits<int>::max()));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [1,2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(
+ doc, "a", boost::none, std::numeric_limits<int>::min()));
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [1,2,3,4]}")},
+ projection_executor_utils::applyFindSliceProjection(
+ doc, "a", std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: []}")},
+ projection_executor_utils::applyFindSliceProjection(
+ doc, "a", std::numeric_limits<int>::max(), std::numeric_limits<int>::max()));
+
+ doc = Document{fromjson("{a: [{b: 1, c: 1}, {b: 2, c: 2}, {b: 3, c: 3}], d: 2}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [{b: 1, c: 1}], d: 2}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, 1));
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [{b: 3, c: 3}], d: 2}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, -1));
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: 2, c: 2}], d: 2}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", 1, 1));
+
+ doc = Document{fromjson("{a: 1}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: 1}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, 2));
+
+ doc = Document{fromjson("{a: {b: 1}}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: 1}}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a", boost::none, 2));
+}
+
+TEST(SliceProjection, CorrectlyProjectsDottedPath) {
+ auto doc = Document{fromjson("{a: {b: [1,2,3], c: 1}}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: [1,2], c: 1}}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, 2));
+
+ doc = Document{fromjson("{a: {b: [1,2,3], c: 1}, d: 1}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: [1,2], c: 1}, d: 1}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, 2));
+
+ doc = Document{fromjson("{a: {b: [[1,2], [3,4], [5,6]]}}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: [[1,2], [3,4]]}}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, 2));
+
+ doc = Document{fromjson("{a: [{b: {c: [1,2,3,4]}}, {b: {c: [5,6,7,8]}}], d: 1}")};
+ ASSERT_DOCUMENT_EQ(Document{fromjson("{a: [{b: {c: [4]}}, {b: {c: [8]}}], d: 1}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b.c", -1, 2));
+
+ doc = Document{fromjson("{a: {b: 1}}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: 1}}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, 2));
+
+ doc = Document{fromjson("{a: {b: {c: 1}}}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: {b: {c: 1}}}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, 2));
+
+ doc = Document{fromjson("{a: [{b: [1,2,3], c: 1}]}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [{b: [3], c: 1}]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, -1));
+
+ doc = Document{fromjson("{a: [{b: [1,2,3], c: 4}, {b: [5,6,7], c: 8}]}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [{b: [3], c: 4}, {b: [7], c: 8}]}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b", boost::none, -1));
+
+ doc = Document{fromjson("{a: [{b: [{x:1, c: [1, 2]}, {y: 1, c: [3, 4]}]}], z: 1}")};
+ ASSERT_DOCUMENT_EQ(
+ Document{fromjson("{a: [{b: [{x:1, c: [1]}, {y: 1, c: [3]}]}], z: 1}")},
+ projection_executor_utils::applyFindSliceProjection(doc, "a.b.c", boost::none, 1));
+}
+} // namespace slice_projection_tests
+} // namespace mongo::projection_executor_utils
diff --git a/src/mongo/db/exec/projection_exec_agg_test.cpp b/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp
index 1a5c11ecc88..4624a3b9b92 100644
--- a/src/mongo/db/exec/projection_exec_agg_test.cpp
+++ b/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp
@@ -29,37 +29,50 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/exec/projection_exec_agg.h"
-
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/json.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
+#include "mongo/db/exec/projection_executor_utils.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/db/query/projection_parser.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
-namespace mongo {
+namespace mongo::projection_executor {
namespace {
-
-using ArrayRecursionPolicy = ProjectionExecAgg::ArrayRecursionPolicy;
-using DefaultIdPolicy = ProjectionExecAgg::DefaultIdPolicy;
-
template <typename T>
BSONObj wrapInLiteral(const T& arg) {
return BSON("$literal" << arg);
}
-// Helper to simplify the creation of a ProjectionExecAgg which includes _id and recurses arrays.
-std::unique_ptr<ProjectionExecAgg> makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
+auto createProjectionExecutor(const BSONObj& spec, const ProjectionPolicies& policies) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto projection = projection_ast::parse(expCtx, spec, policies);
+ auto executor =
+ buildProjectionExecutor(expCtx, &projection, policies, true /* optimizeExecutor */);
+ return executor;
+}
+
+// Helper to simplify the creation of a ProjectionExecutor which includes _id and recurses arrays.
+std::unique_ptr<ProjectionExecutor> makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSONObj projSpec) {
- return ProjectionExecAgg::create(
- projSpec, DefaultIdPolicy::kIncludeId, ArrayRecursionPolicy::kRecurseNestedArrays);
+ ProjectionPolicies policies{ProjectionPolicies::DefaultIdPolicy::kIncludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays,
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields,
+ ProjectionPolicies::FindOnlyFeaturesPolicy::kBanFindOnlyFeatures};
+ return createProjectionExecutor(projSpec, policies);
}
-// Helper to simplify the creation of a ProjectionExecAgg which excludes _id and recurses arrays.
-std::unique_ptr<ProjectionExecAgg> makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
+// Helper to simplify the creation of a ProjectionExecutor which excludes _id and recurses arrays.
+std::unique_ptr<ProjectionExecutor> makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
BSONObj projSpec) {
- return ProjectionExecAgg::create(
- projSpec, DefaultIdPolicy::kExcludeId, ArrayRecursionPolicy::kRecurseNestedArrays);
+ ProjectionPolicies policies{ProjectionPolicies::DefaultIdPolicy::kExcludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kRecurseNestedArrays,
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields,
+ ProjectionPolicies::FindOnlyFeaturesPolicy::kBanFindOnlyFeatures};
+ return createProjectionExecutor(projSpec, policies);
}
std::set<FieldRef> toFieldRefs(const std::set<std::string>& stringPaths) {
@@ -75,7 +88,7 @@ std::set<FieldRef> toFieldRefs(const std::set<std::string>& stringPaths) {
// Error cases.
//
-TEST(ProjectionExecAggErrors, ShouldRejectMixOfInclusionAndComputedFields) {
+TEST(ProjectionExecutorErrors, ShouldRejectMixOfInclusionAndComputedFields) {
ASSERT_THROWS(makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("a" << true << "b" << wrapInLiteral(1))),
AssertionException);
@@ -101,7 +114,7 @@ TEST(ProjectionExecAggErrors, ShouldRejectMixOfInclusionAndComputedFields) {
AssertionException);
}
-TEST(ProjectionExecAggErrors, ShouldRejectMixOfExclusionAndComputedFields) {
+TEST(ProjectionExecutorErrors, ShouldRejectMixOfExclusionAndComputedFields) {
ASSERT_THROWS(makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("a" << false << "b" << wrapInLiteral(1))),
AssertionException);
@@ -127,7 +140,7 @@ TEST(ProjectionExecAggErrors, ShouldRejectMixOfExclusionAndComputedFields) {
AssertionException);
}
-TEST(ProjectionExecAggErrors, ShouldRejectOnlyComputedFields) {
+TEST(ProjectionExecutorErrors, ShouldRejectOnlyComputedFields) {
ASSERT_THROWS(makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("a" << wrapInLiteral(1) << "b" << wrapInLiteral(1))),
AssertionException);
@@ -143,180 +156,180 @@ TEST(ProjectionExecAggErrors, ShouldRejectOnlyComputedFields) {
// Valid projections.
-TEST(ProjectionExecAggType, ShouldAcceptInclusionProjection) {
+TEST(ProjectionExecutorType, ShouldAcceptInclusionProjection) {
auto parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(BSON("a" << true));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("_id" << false << "a" << true));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("_id" << false << "a.b.c" << true));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(BSON("_id.x" << true));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("_id" << BSON("x" << true)));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("x" << BSON("_id" << true)));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
}
-TEST(ProjectionExecAggType, ShouldAcceptExclusionProjection) {
+TEST(ProjectionExecutorType, ShouldAcceptExclusionProjection) {
auto parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(BSON("a" << false));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(BSON("_id.x" << false));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("_id" << BSON("x" << false)));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
BSON("x" << BSON("_id" << false)));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(BSON("_id" << false));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
}
// Misc tests.
-TEST(ProjectionExecAggTests, InclusionFieldPathsWithImplicitIdInclusion) {
+TEST(ProjectionExecutorTests, InclusionFieldPathsWithImplicitIdInclusion) {
auto parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
fromjson("{a: {b: {c: 1}}, d: 1}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
std::set<FieldRef> expectedPaths = toFieldRefs({"_id", "a.b.c", "d"});
// Verify that the exhaustive set of paths is as expected.
ASSERT(exhaustivePaths == expectedPaths);
}
-TEST(ProjectionExecAggTests, InclusionFieldPathsWithExplicitIdInclusion) {
+TEST(ProjectionExecutorTests, InclusionFieldPathsWithExplicitIdInclusion) {
auto parsedProject = makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
fromjson("{_id: 1, a: {b: {c: 1}}, d: 1}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
std::set<FieldRef> expectedPaths = toFieldRefs({"_id", "a.b.c", "d"});
// Verify that the exhaustive set of paths is as expected.
ASSERT(exhaustivePaths == expectedPaths);
}
-TEST(ProjectionExecAggTests, InclusionFieldPathsWithExplicitIdInclusionIdOnly) {
+TEST(ProjectionExecutorTests, InclusionFieldPathsWithExplicitIdInclusionIdOnly) {
auto parsedProject =
makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(fromjson("{_id: 1}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
std::set<FieldRef> expectedPaths = toFieldRefs({"_id"});
// Verify that the exhaustive set of paths is as expected.
ASSERT(exhaustivePaths == expectedPaths);
}
-TEST(ProjectionExecAggTests, InclusionFieldPathsWithImplicitIdExclusion) {
+TEST(ProjectionExecutorTests, InclusionFieldPathsWithImplicitIdExclusion) {
auto parsedProject = makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
fromjson("{a: {b: {c: 1}}, d: 1}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
std::set<FieldRef> expectedPaths = toFieldRefs({"a.b.c", "d"});
// Verify that the exhaustive set of paths is as expected.
ASSERT(exhaustivePaths == expectedPaths);
}
-TEST(ProjectionExecAggTests, InclusionFieldPathsWithExplicitIdExclusion) {
+TEST(ProjectionExecutorTests, InclusionFieldPathsWithExplicitIdExclusion) {
auto parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
fromjson("{_id: 0, a: {b: {c: 1}}, d: 1}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kInclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
std::set<FieldRef> expectedPaths = toFieldRefs({"a.b.c", "d"});
// Verify that the exhaustive set of paths is as expected.
ASSERT(exhaustivePaths == expectedPaths);
}
-TEST(ProjectionExecAggTests, ExclusionFieldPathsWithImplicitIdInclusion) {
+TEST(ProjectionExecutorTests, ExclusionFieldPathsWithImplicitIdInclusion) {
auto parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
fromjson("{a: {b: {c: 0}}, d: 0}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
// Verify that the exhaustive set is empty, despite the implicit inclusion of _id.
ASSERT(exhaustivePaths.empty());
}
-TEST(ProjectionExecAggTests, ExclusionFieldPathsWithExplicitIdInclusion) {
+TEST(ProjectionExecutorTests, ExclusionFieldPathsWithExplicitIdInclusion) {
auto parsedProject = makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
fromjson("{_id: 1, a: {b: {c: 0}}, d: 0}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
// Verify that the exhaustive set is empty, despite the explicit inclusion of _id.
ASSERT(exhaustivePaths.empty());
}
-TEST(ProjectionExecAggTests, ExclusionFieldPathsWithImplicitIdExclusion) {
+TEST(ProjectionExecutorTests, ExclusionFieldPathsWithImplicitIdExclusion) {
auto parsedProject = makeProjectionWithDefaultIdExclusionAndNestedArrayRecursion(
fromjson("{a: {b: {c: 0}}, d: 0}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
// Verify that the exhaustive set is empty.
ASSERT(exhaustivePaths.empty());
}
-TEST(ProjectionExecAggTests, ExclusionFieldPathsWithExplicitIdExclusion) {
+TEST(ProjectionExecutorTests, ExclusionFieldPathsWithExplicitIdExclusion) {
auto parsedProject = makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(
fromjson("{_id: 1, a: {b: {c: 0}}, d: 0}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
// Verify that the exhaustive set is empty.
ASSERT(exhaustivePaths.empty());
}
-TEST(ProjectionExecAggTests, ExclusionFieldPathsWithExplicitIdExclusionIdOnly) {
+TEST(ProjectionExecutorTests, ExclusionFieldPathsWithExplicitIdExclusionIdOnly) {
auto parsedProject =
makeProjectionWithDefaultIdInclusionAndNestedArrayRecursion(fromjson("{_id: 0}"));
- ASSERT(parsedProject->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection);
+ ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kExclusionProjection);
// Extract the exhaustive set of paths that will be preserved by the projection.
- auto exhaustivePaths = parsedProject->getExhaustivePaths();
+ auto exhaustivePaths = projection_executor_utils::extractExhaustivePaths(parsedProject.get());
// Verify that the exhaustive set is empty.
ASSERT(exhaustivePaths.empty());
}
} // namespace
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp b/src/mongo/db/exec/projection_node.cpp
index 941a6f2deb8..b2fb949cea9 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_node.cpp
+++ b/src/mongo/db/exec/projection_node.cpp
@@ -29,11 +29,9 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection_node.h"
-
-namespace mongo {
-namespace parsed_aggregation_projection {
+#include "mongo/db/exec/projection_node.h"
+namespace mongo::projection_executor {
using ArrayRecursionPolicy = ProjectionPolicies::ArrayRecursionPolicy;
using ComputedFieldsPolicy = ProjectionPolicies::ComputedFieldsPolicy;
using DefaultIdPolicy = ProjectionPolicies::DefaultIdPolicy;
@@ -284,6 +282,4 @@ void ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explai
}
}
}
-
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_node.h b/src/mongo/db/exec/projection_node.h
index 3e77720858a..2a3f739cb9b 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_node.h
+++ b/src/mongo/db/exec/projection_node.h
@@ -29,13 +29,11 @@
#pragma once
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
+#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/query/projection_policies.h"
-namespace mongo {
-namespace parsed_aggregation_projection {
-
+namespace mongo::projection_executor {
/**
* A node used to define the parsed structure of a projection. Each ProjectionNode represents one
* 'level' of the parsed specification. The root ProjectionNode represents all top level projections
@@ -197,6 +195,4 @@ private:
// example above, '_orderToProcessAdditionsAndChildren' would be ["a", "b", "d"].
std::vector<std::string> _orderToProcessAdditionsAndChildren;
};
-
-} // namespace parsed_aggregation_projection
-} // namespace mongo
+} // namespace mongo::projection_executor
diff --git a/src/mongo/db/index/SConscript b/src/mongo/db/index/SConscript
index 9dbc5affd53..c681e21fa3c 100644
--- a/src/mongo/db/index/SConscript
+++ b/src/mongo/db/index/SConscript
@@ -46,14 +46,15 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/db/bson/dotted_path_support',
+ '$BUILD_DIR/mongo/db/exec/projection_executor',
'$BUILD_DIR/mongo/db/exec/working_set',
'$BUILD_DIR/mongo/db/fts/base_fts',
'$BUILD_DIR/mongo/db/geo/geoparser',
'$BUILD_DIR/mongo/db/index_names',
'$BUILD_DIR/mongo/db/mongohasher',
'$BUILD_DIR/mongo/db/pipeline/document_path_support',
- '$BUILD_DIR/mongo/db/projection_exec_agg',
'$BUILD_DIR/mongo/db/query/collation/collator_interface',
+ '$BUILD_DIR/mongo/db/query/projection_ast',
'$BUILD_DIR/mongo/db/query/sort_pattern',
'$BUILD_DIR/third_party/s2/s2',
'expression_params',
diff --git a/src/mongo/db/index/wildcard_access_method.h b/src/mongo/db/index/wildcard_access_method.h
index 69ca0ad80dd..09a3f5dc530 100644
--- a/src/mongo/db/index/wildcard_access_method.h
+++ b/src/mongo/db/index/wildcard_access_method.h
@@ -71,10 +71,10 @@ public:
const MultikeyPaths& multikeyPaths) const final;
/**
- * Returns a pointer to the ProjectionExecAgg owned by the underlying WildcardKeyGenerator.
+ * Returns a pointer to the ProjectionExecutor owned by the underlying WildcardKeyGenerator.
*/
- const ProjectionExecAgg* getProjectionExec() const {
- return _keyGen.getProjectionExec();
+ projection_executor::ProjectionExecutor* getProjectionExecutor() const {
+ return _keyGen.getProjectionExecutor();
}
/**
diff --git a/src/mongo/db/index/wildcard_key_generator.cpp b/src/mongo/db/index/wildcard_key_generator.cpp
index 63607683c60..116043c2fe4 100644
--- a/src/mongo/db/index/wildcard_key_generator.cpp
+++ b/src/mongo/db/index/wildcard_key_generator.cpp
@@ -31,8 +31,11 @@
#include "mongo/db/index/wildcard_key_generator.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/query/collation/collation_index_key.h"
+#include "mongo/db/query/projection_parser.h"
namespace mongo {
namespace {
@@ -62,8 +65,8 @@ void popPathComponent(BSONElement elem, bool enclosingObjIsArray, FieldRef* path
constexpr StringData WildcardKeyGenerator::kSubtreeSuffix;
-std::unique_ptr<ProjectionExecAgg> WildcardKeyGenerator::createProjectionExec(
- BSONObj keyPattern, BSONObj pathProjection) {
+std::unique_ptr<projection_executor::ProjectionExecutor>
+WildcardKeyGenerator::createProjectionExecutor(BSONObj keyPattern, BSONObj pathProjection) {
// We should never have a key pattern that contains more than a single element.
invariant(keyPattern.nFields() == 1);
@@ -82,13 +85,14 @@ std::unique_ptr<ProjectionExecAgg> WildcardKeyGenerator::createProjectionExec(
? BSON(indexRoot.substr(0, suffixPos) << 1)
: pathProjection.isEmpty() ? kDefaultProjection : pathProjection);
- // If the projection spec does not explicitly specify _id, we exclude it by default. We also
- // prevent the projection from recursing through nested arrays, in order to ensure that the
- // output document aligns with the match system's expectations.
- return ProjectionExecAgg::create(
- projSpec,
- ProjectionExecAgg::DefaultIdPolicy::kExcludeId,
- ProjectionExecAgg::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ // Construct a dummy ExpressionContext for ProjectionExecutor. It's OK to set the
+ // ExpressionContext's OperationContext and CollatorInterface to 'nullptr' here; since we
+ // ban computed fields from the projection, the ExpressionContext will never be used.
+ auto expCtx = make_intrusive<ExpressionContext>(nullptr, nullptr);
+ auto policies = ProjectionPolicies::wildcardIndexSpecProjectionPolicies();
+ auto projection = projection_ast::parse(expCtx, projSpec, policies);
+ return projection_executor::buildProjectionExecutor(
+ expCtx, &projection, policies, true /* optimizeExecutor */);
}
WildcardKeyGenerator::WildcardKeyGenerator(BSONObj keyPattern,
@@ -96,20 +100,23 @@ WildcardKeyGenerator::WildcardKeyGenerator(BSONObj keyPattern,
const CollatorInterface* collator,
KeyString::Version keyStringVersion,
Ordering ordering)
- : _collator(collator),
+ : _projExec(createProjectionExecutor(keyPattern, pathProjection)),
+ _collator(collator),
_keyPattern(keyPattern),
_keyStringVersion(keyStringVersion),
- _ordering(ordering) {
- _projExec = createProjectionExec(keyPattern, pathProjection);
-}
+ _ordering(ordering) {}
void WildcardKeyGenerator::generateKeys(BSONObj inputDoc,
KeyStringSet* keys,
KeyStringSet* multikeyPaths,
boost::optional<RecordId> id) const {
FieldRef rootPath;
- _traverseWildcard(
- _projExec->applyProjection(inputDoc), false, &rootPath, keys, multikeyPaths, id);
+ _traverseWildcard(_projExec->applyTransformation(Document{inputDoc}).toBson(),
+ false,
+ &rootPath,
+ keys,
+ multikeyPaths,
+ id);
}
void WildcardKeyGenerator::_traverseWildcard(BSONObj obj,
diff --git a/src/mongo/db/index/wildcard_key_generator.h b/src/mongo/db/index/wildcard_key_generator.h
index f5c8524c751..cf99187e35f 100644
--- a/src/mongo/db/index/wildcard_key_generator.h
+++ b/src/mongo/db/index/wildcard_key_generator.h
@@ -29,7 +29,7 @@
#pragma once
-#include "mongo/db/exec/projection_exec_agg.h"
+#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/field_ref.h"
#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/db/storage/key_string.h"
@@ -47,12 +47,12 @@ public:
static constexpr StringData kSubtreeSuffix = ".$**"_sd;
/**
- * Returns an owned ProjectionExecAgg identical to the one that WildcardKeyGenerator will use
+ * Returns an owned ProjectionExecutor identical to the one that WildcardKeyGenerator will use
* internally when generating the keys for the $** index, as defined by the 'keyPattern' and
* 'pathProjection' arguments.
*/
- static std::unique_ptr<ProjectionExecAgg> createProjectionExec(BSONObj keyPattern,
- BSONObj pathProjection);
+ static std::unique_ptr<projection_executor::ProjectionExecutor> createProjectionExecutor(
+ BSONObj keyPattern, BSONObj pathProjection);
WildcardKeyGenerator(BSONObj keyPattern,
BSONObj pathProjection,
@@ -61,9 +61,9 @@ public:
Ordering ordering);
/**
- * Returns a pointer to the key generator's underlying ProjectionExecAgg.
+ * Returns a pointer to the key generator's underlying ProjectionExecutor.
*/
- const ProjectionExecAgg* getProjectionExec() const {
+ projection_executor::ProjectionExecutor* getProjectionExecutor() const {
return _projExec.get();
}
@@ -107,7 +107,7 @@ private:
KeyStringSet* keys,
boost::optional<RecordId> id) const;
- std::unique_ptr<ProjectionExecAgg> _projExec;
+ std::unique_ptr<projection_executor::ProjectionExecutor> _projExec;
const CollatorInterface* _collator;
const BSONObj _keyPattern;
const KeyString::Version _keyStringVersion;
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index ee2fb4ac34e..13da4edb4aa 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -334,6 +334,7 @@ pipelineEnv.Library(
'$BUILD_DIR/mongo/db/curop',
'$BUILD_DIR/mongo/db/curop_failpoint_helpers',
'$BUILD_DIR/mongo/db/exec/document_value/document_value',
+ '$BUILD_DIR/mongo/db/exec/projection_executor',
'$BUILD_DIR/mongo/db/exec/scoped_timer',
'$BUILD_DIR/mongo/db/exec/sort_executor',
'$BUILD_DIR/mongo/db/generic_cursor',
@@ -342,7 +343,6 @@ pipelineEnv.Library(
'$BUILD_DIR/mongo/db/logical_session_id_helpers',
'$BUILD_DIR/mongo/db/matcher/expressions',
'$BUILD_DIR/mongo/db/pipeline/lite_parsed_document_source',
- '$BUILD_DIR/mongo/db/projection_executor',
'$BUILD_DIR/mongo/db/query/collation/collator_factory_interface',
'$BUILD_DIR/mongo/db/query/collation/collator_interface',
'$BUILD_DIR/mongo/db/query/sort_pattern',
@@ -364,7 +364,6 @@ pipelineEnv.Library(
'expression_context',
'expression_javascript',
'granularity_rounder',
- 'parsed_aggregation_projection',
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/commands/test_commands_enabled',
@@ -373,19 +372,6 @@ pipelineEnv.Library(
)
env.Library(
- target='parsed_aggregation_projection',
- source=[
- 'parsed_aggregation_projection_node.cpp',
- 'parsed_add_fields.cpp',
- ],
- LIBDEPS=[
- 'expression',
- 'field_path',
- '$BUILD_DIR/mongo/db/matcher/expressions',
- ]
-)
-
-env.Library(
target='runtime_constants_idl',
source=[
env.Idlc('runtime_constants.idl')[0]
@@ -477,11 +463,6 @@ env.CppUnitTest(
'granularity_rounder_preferred_numbers_test.cpp',
'lookup_set_cache_test.cpp',
'mongos_process_interface_test.cpp',
- 'parsed_add_fields_test.cpp',
- 'parsed_aggregation_projection_test.cpp',
- 'parsed_exclusion_projection_test.cpp',
- 'parsed_find_projection_test.cpp',
- 'parsed_inclusion_projection_test.cpp',
'pipeline_metadata_tree_test.cpp',
'pipeline_test.cpp',
'process_interface_standalone_test.cpp',
@@ -497,7 +478,6 @@ env.CppUnitTest(
'$BUILD_DIR/mongo/db/auth/authmocks',
'$BUILD_DIR/mongo/db/exec/document_value/document_value',
'$BUILD_DIR/mongo/db/exec/document_value/document_value_test_util',
- '$BUILD_DIR/mongo/db/projection_executor',
'$BUILD_DIR/mongo/db/query/collation/collator_interface_mock',
'$BUILD_DIR/mongo/db/query/query_test_service_context',
'$BUILD_DIR/mongo/db/repl/oplog_entry',
@@ -520,7 +500,6 @@ env.CppUnitTest(
'mongo_process_common',
'mongo_process_interface',
'mongos_process_interface',
- 'parsed_aggregation_projection',
'pipeline',
'process_interface_shardsvr',
'process_interface_standalone',
diff --git a/src/mongo/db/pipeline/document_source_add_fields.cpp b/src/mongo/db/pipeline/document_source_add_fields.cpp
index a362f38495d..2e2b4a5a53d 100644
--- a/src/mongo/db/pipeline/document_source_add_fields.cpp
+++ b/src/mongo/db/pipeline/document_source_add_fields.cpp
@@ -34,13 +34,11 @@
#include <boost/optional.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>
+#include "mongo/db/exec/add_fields_projection_executor.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
-#include "mongo/db/pipeline/parsed_add_fields.h"
namespace mongo {
-
using boost::intrusive_ptr;
-using parsed_aggregation_projection::ParsedAddFields;
REGISTER_DOCUMENT_SOURCE(addFields,
LiteParsedDocumentSourceDefault::parse,
@@ -60,7 +58,8 @@ intrusive_ptr<DocumentSource> DocumentSourceAddFields::create(
expCtx,
[&]() {
try {
- return ParsedAddFields::create(expCtx, addFieldsSpec);
+ return projection_executor::AddFieldsProjectionExecutor::create(expCtx,
+ addFieldsSpec);
} catch (DBException& ex) {
ex.addContext("Invalid " + userSpecifiedName.toString());
throw;
diff --git a/src/mongo/db/pipeline/document_source_add_fields_test.cpp b/src/mongo/db/pipeline/document_source_add_fields_test.cpp
index e20820feb0d..b5a1dd85baa 100644
--- a/src/mongo/db/pipeline/document_source_add_fields_test.cpp
+++ b/src/mongo/db/pipeline/document_source_add_fields_test.cpp
@@ -47,10 +47,9 @@ namespace {
using std::vector;
//
-// DocumentSourceAddFields delegates much of its responsibilities to the ParsedAddFields, which
-// derives from ParsedAggregationProjection.
-// Most of the functional tests are testing ParsedAddFields directly. These are meant as
-// simpler integration tests.
+// DocumentSourceAddFields delegates much of its responsibilities to the
+// AddFieldsProjectionExecutor. Most of the functional tests are testing
+// AddFieldsProjectionExecutor. directly. These are meant as simpler integration tests.
//
// This provides access to getExpCtx(), but we'll use a different name for this test suite.
diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp
index a626eb2b41f..b19eba77aec 100644
--- a/src/mongo/db/pipeline/document_source_project.cpp
+++ b/src/mongo/db/pipeline/document_source_project.cpp
@@ -35,14 +35,13 @@
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
#include "mongo/db/query/projection_parser.h"
namespace mongo {
using boost::intrusive_ptr;
-using ParsedAggregationProjection = parsed_aggregation_projection::ParsedAggregationProjection;
REGISTER_DOCUMENT_SOURCE(project,
LiteParsedDocumentSourceDefault::parse,
@@ -68,10 +67,10 @@ intrusive_ptr<DocumentSource> DocumentSourceProject::create(
intrusive_ptr<DocumentSource> project(new DocumentSourceSingleDocumentTransformation(
expCtx,
[&]() {
- // The ParsedAggregationProjection will internally perform a check to see if the
- // provided specification is valid, and throw an exception if it was not. The exception
- // is caught here so we can add the name that was actually specified by the user, be it
- // $project or an alias.
+ // The ProjectionExecutor will internally perform a check to see if the provided
+ // specification is valid, and throw an exception if it was not. The exception is caught
+ // here so we can add the name that was actually specified by the user, be it $project
+ // or an alias.
try {
auto policies = ProjectionPolicies::aggregateProjectionPolicies();
auto projection = projection_ast::parse(expCtx, projectSpec, policies);
diff --git a/src/mongo/db/pipeline/document_source_project_test.cpp b/src/mongo/db/pipeline/document_source_project_test.cpp
index 9c04482340e..ff4fa9d6b5f 100644
--- a/src/mongo/db/pipeline/document_source_project_test.cpp
+++ b/src/mongo/db/pipeline/document_source_project_test.cpp
@@ -51,9 +51,9 @@ using boost::intrusive_ptr;
using std::vector;
//
-// DocumentSourceProject delegates much of its responsibilities to the ParsedAggregationProjection.
-// Most of the functional tests are testing ParsedAggregationProjection directly. These are meant as
-// simpler integration tests.
+// DocumentSourceProject delegates much of its responsibilities to the ProjectionExecutor. Most of
+// the functional tests are testing ProjectionExecutor directly. These are meant as simpler
+// integration tests.
//
// This provides access to getExpCtx(), but we'll use a different name for this test suite.
diff --git a/src/mongo/db/pipeline/expression_find_internal.h b/src/mongo/db/pipeline/expression_find_internal.h
index 794d5ebc912..fe9f4162c89 100644
--- a/src/mongo/db/pipeline/expression_find_internal.h
+++ b/src/mongo/db/pipeline/expression_find_internal.h
@@ -31,7 +31,7 @@
#include <fmt/format.h>
-#include "mongo/db/exec/find_projection_executor.h"
+#include "mongo/db/exec/projection_executor_utils.h"
#include "mongo/db/matcher/copyable_match_expression.h"
#include "mongo/db/pipeline/expression.h"
@@ -66,7 +66,7 @@ public:
"Positional operator post-image can only be an object, but got {}"_format(
typeName(postImage.getType())),
postImage.getType() == BSONType::Object);
- return Value{projection_executor::applyPositionalProjection(
+ return Value{projection_executor_utils::applyFindPositionalProjection(
preImage.getDocument(), postImage.getDocument(), *_matchExpr, _path)};
}
@@ -137,7 +137,7 @@ public:
"$slice operator can only be applied to an object, but got {}"_format(
typeName(postImage.getType())),
postImage.getType() == BSONType::Object);
- return Value{projection_executor::applySliceProjection(
+ return Value{projection_executor_utils::applyFindSliceProjection(
postImage.getDocument(), _path, _skip, _limit)};
}
@@ -197,7 +197,7 @@ public:
"$elemMatch operator can only be applied to an object, but got {}"_format(
typeName(input.getType())),
input.getType() == BSONType::Object);
- return projection_executor::applyElemMatchProjection(
+ return projection_executor_utils::applyFindElemMatchProjection(
input.getDocument(), *_matchExpr, _path);
}
diff --git a/src/mongo/db/pipeline/expression_find_internal_test.cpp b/src/mongo/db/pipeline/expression_find_internal_test.cpp
index 0fe1e34e9c5..62b79968731 100644
--- a/src/mongo/db/pipeline/expression_find_internal_test.cpp
+++ b/src/mongo/db/pipeline/expression_find_internal_test.cpp
@@ -30,14 +30,14 @@
#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/pipeline/expression_find_internal.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
#include "mongo/unittest/unittest.h"
namespace mongo::expression_internal_tests {
constexpr auto kProjectionPostImageVarName =
- parsed_aggregation_projection::ParsedAggregationProjection::kProjectionPostImageVarName;
+ projection_executor::ProjectionExecutor::kProjectionPostImageVarName;
auto defineAndSetProjectionPostImageVariable(boost::intrusive_ptr<ExpressionContext> expCtx,
Value postImage) {
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.h b/src/mongo/db/pipeline/parsed_aggregation_projection.h
deleted file mode 100644
index 5c7e2a24b44..00000000000
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * Copyright (C) 2018-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.
- */
-
-#pragma once
-
-#include "mongo/platform/basic.h"
-
-#include <boost/intrusive_ptr.hpp>
-#include <memory>
-
-#include "mongo/bson/bsonelement.h"
-#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/field_path.h"
-#include "mongo/db/pipeline/transformer_interface.h"
-#include "mongo/db/query/projection_policies.h"
-
-namespace mongo {
-
-class BSONObj;
-class Document;
-class ExpressionContext;
-
-namespace parsed_aggregation_projection {
-/**
- * A ParsedAggregationProjection is responsible for parsing and executing a $project. It
- * represents either an inclusion or exclusion projection. This is the common interface between the
- * two types of projections.
- */
-class ParsedAggregationProjection : public TransformerInterface {
-public:
- /**
- * The name of an internal variable to bind a projection post image to, which is used by the
- * '_rootReplacementExpression' to replace the content of the transformed document.
- */
- static constexpr StringData kProjectionPostImageVarName{"INTERNAL_PROJ_POST_IMAGE"_sd};
-
- /**
- * Optimize any expressions contained within this projection.
- */
- virtual void optimize() {
- if (_rootReplacementExpression) {
- _rootReplacementExpression->optimize();
- }
- }
-
- /**
- * Add any dependencies needed by this projection or any sub-expressions to 'deps'.
- */
- virtual DepsTracker::State addDependencies(DepsTracker* deps) const {
- return DepsTracker::State::NOT_SUPPORTED;
- }
-
- /**
- * Apply the projection transformation.
- */
- Document applyTransformation(const Document& input) {
- auto output = applyProjection(input);
- if (_rootReplacementExpression) {
- return _applyRootReplacementExpression(input, output);
- }
- return output;
- }
-
- /**
- * Sets 'expr' as a root-replacement expression to this tree. A root-replacement expression,
- * once evaluated, will replace an entire output document. A projection post image document
- * will be accessible via the special variable, whose name is stored in
- * 'kProjectionPostImageVarName', if this expression needs access to it.
- */
- void setRootReplacementExpression(boost::intrusive_ptr<Expression> expr) {
- _rootReplacementExpression = expr;
- }
-
-protected:
- ParsedAggregationProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies)
- : _expCtx(expCtx),
- _policies(policies),
- _projectionPostImageVarId{
- _expCtx->variablesParseState.defineVariable(kProjectionPostImageVarName)} {}
-
- /**
- * Apply the projection to 'input'.
- */
- virtual Document applyProjection(const Document& input) const = 0;
-
- boost::intrusive_ptr<ExpressionContext> _expCtx;
-
- ProjectionPolicies _policies;
-
- boost::intrusive_ptr<Expression> _rootReplacementExpression;
-
-private:
- Document _applyRootReplacementExpression(const Document& input, const Document& output) {
- using namespace fmt::literals;
-
- _expCtx->variables.setValue(_projectionPostImageVarId, Value{output});
- auto val = _rootReplacementExpression->evaluate(input, &_expCtx->variables);
- uassert(51254,
- "Root-replacement expression must return a document, but got {}"_format(
- typeName(val.getType())),
- val.getType() == BSONType::Object);
- return val.getDocument();
- }
-
- // This variable id is used to bind a projection post-image so that it can be accessed by
- // root-replacement expressions which apply projection to the entire post-image document, rather
- // than to a specific field.
- Variables::Id _projectionPostImageVarId;
-};
-} // namespace parsed_aggregation_projection
-} // namespace mongo
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp b/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
deleted file mode 100644
index 337d9f74307..00000000000
--- a/src/mongo/db/pipeline/parsed_aggregation_projection_test.cpp
+++ /dev/null
@@ -1,612 +0,0 @@
-/**
- * Copyright (C) 2018-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/pipeline/parsed_aggregation_projection.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/projection_executor.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
-#include "mongo/db/query/projection_parser.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace parsed_aggregation_projection {
-namespace {
-
-template <typename T>
-BSONObj wrapInLiteral(const T& arg) {
- return BSON("$literal" << arg);
-}
-
-// Helper to simplify the creation of a ParsedAggregationProjection 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 projection_executor::buildProjectionExecutor(
- expCtx, &projection, defaultPolicies, true /* optimizeExecutor */);
-}
-
-// Helper to simplify the creation of a ParsedAggregationProjection 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 projection_executor::buildProjectionExecutor(
- expCtx, &projection, banComputedFields, true /* optimizeExecutor */);
-}
-
-//
-// Error cases.
-//
-
-TEST(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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);
-
- // 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(ParsedAggregationProjectionErrors, 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);
-
- // 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, ShouldRejectTopLevelExpressions) {
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("$add" << BSON_ARRAY(4 << 2))),
- AssertionException);
-}
-
-TEST(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, ShouldRejectEmptyProjection) {
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSONObj()), AssertionException);
-}
-
-TEST(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, 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(ParsedAggregationProjectionErrors, ShouldErrorOnInvalidFieldPath) {
- // Empty field names.
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << wrapInLiteral(2))),
- AssertionException);
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << true)), AssertionException);
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("" << false)), AssertionException);
-
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("" << true))),
- AssertionException);
- ASSERT_THROWS(makeProjectionWithDefaultPolicies(BSON("a" << BSON("" << false))),
- AssertionException);
-
- 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(ParsedAggregationProjectionErrors, ShouldNotErrorOnTwoNestedFields) {
- makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a.c" << true));
- makeProjectionWithDefaultPolicies(BSON("a.b" << true << "a" << BSON("c" << true)));
-}
-
-//
-// Determining exclusion vs. inclusion.
-//
-
-TEST(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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);
-
- parsedProject = makeProjectionWithDefaultPolicies(
- BSON("a" << BSON("b" << true << "c" << wrapInLiteral(1))));
- ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
-
- parsedProject = makeProjectionWithDefaultPolicies(BSON("a" << BSON("b" << true << "c"
- << "stringLiteral")));
- ASSERT(parsedProject->getType() == TransformerInterface::TransformerType::kInclusionProjection);
-}
-
-TEST(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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(ParsedAggregationProjectionType, 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 parsed_aggregation_projection
-} // namespace mongo
diff --git a/src/mongo/db/pipeline/parsed_find_projection_test.cpp b/src/mongo/db/pipeline/parsed_find_projection_test.cpp
deleted file mode 100644
index 27e2ef8c3f8..00000000000
--- a/src/mongo/db/pipeline/parsed_find_projection_test.cpp
+++ /dev/null
@@ -1,282 +0,0 @@
-/**
- * Copyright (C) 2019-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/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/pipeline/expression_find_internal.h"
-#include "mongo/db/pipeline/parsed_aggregation_projection.h"
-#include "mongo/db/query/projection_parser.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo::parsed_aggregation_projection {
-constexpr auto kProjectionPostImageVarName =
- parsed_aggregation_projection::ParsedAggregationProjection::kProjectionPostImageVarName;
-
-auto createProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- const BSONObj& projSpec,
- ProjectionPolicies policies) {
- auto projection = projection_ast::parse(expCtx, projSpec, policies);
- return projection_executor::buildProjectionExecutor(
- expCtx, &projection, policies, true /* optimizeExecutor */);
-}
-
-class PositionalProjectionExecutionTest : public AggregationContextFixture {
-protected:
- auto applyPositional(const BSONObj& projSpec,
- const BSONObj& matchSpec,
- const std::string& path,
- const Document& input) {
- auto executor = createProjectionExecutor(getExpCtx(), projSpec, {});
- auto matchExpr = CopyableMatchExpression{matchSpec,
- getExpCtx(),
- std::make_unique<ExtensionsCallbackNoop>(),
- MatchExpressionParser::kBanAllSpecialFeatures};
- auto expr = make_intrusive<ExpressionInternalFindPositional>(
- getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- path,
- std::move(matchExpr));
- executor->setRootReplacementExpression(expr);
- return executor->applyTransformation(input);
- }
-};
-
-class SliceProjectionExecutionTest : public AggregationContextFixture {
-protected:
- auto applySlice(const BSONObj& projSpec,
- const std::string& path,
- boost::optional<int> skip,
- int limit,
- const Document& input) {
- auto executor = createProjectionExecutor(getExpCtx(), projSpec, {});
- auto expr = make_intrusive<ExpressionInternalFindSlice>(
- getExpCtx(),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- path,
- skip,
- limit);
- executor->setRootReplacementExpression(expr);
- return executor->applyTransformation(input);
- }
-};
-
-TEST_F(PositionalProjectionExecutionTest, CanApplyPositionalWithInclusionProjection) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{foo: [6]}")},
- applyPositional(fromjson("{foo: 1}"),
- fromjson("{foo: {$gte: 5}}"),
- "foo",
- Document{fromjson("{foo: [1,2,6,10]}")}));
-
- ASSERT_DOCUMENT_EQ(Document{fromjson("{bar:1, foo: [6]}")},
- applyPositional(fromjson("{bar: 1, foo: 1}"),
- fromjson("{bar: 1, foo: {$gte: 5}}"),
- "foo",
- Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
-}
-
-TEST_F(PositionalProjectionExecutionTest, AppliesProjectionToPreImage) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{b: [6], c: 'abc'}")},
- applyPositional(fromjson("{b: 1, c: 1}"),
- fromjson("{a: 1, b: {$gte: 5}}"),
- "b",
- Document{fromjson("{a: 1, b: [1,2,6,10], c: 'abc'}")}));
-}
-
-TEST_F(PositionalProjectionExecutionTest, ShouldAddInclusionFieldsAndWholeDocumentToDependencies) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
- auto matchSpec = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
- auto matchExpr = CopyableMatchExpression{matchSpec,
- getExpCtx(),
- std::make_unique<ExtensionsCallbackNoop>(),
- MatchExpressionParser::kBanAllSpecialFeatures};
- auto expr = make_intrusive<ExpressionInternalFindPositional>(
- getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- std::move(matchExpr));
- executor->setRootReplacementExpression(expr);
-
- DepsTracker deps;
- executor->addDependencies(&deps);
-
- ASSERT_EQ(deps.fields.size(), 2UL);
- ASSERT_EQ(deps.fields.count("bar"), 1UL);
- ASSERT_EQ(deps.fields.count("foo.bar"), 1UL);
- ASSERT(deps.needWholeDocument);
-}
-
-TEST_F(PositionalProjectionExecutionTest, ShouldConsiderAllPathsAsModified) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
- auto matchSpec = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
- auto matchExpr = CopyableMatchExpression{matchSpec,
- getExpCtx(),
- std::make_unique<ExtensionsCallbackNoop>(),
- MatchExpressionParser::kBanAllSpecialFeatures};
- auto expr = make_intrusive<ExpressionInternalFindPositional>(
- getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- std::move(matchExpr));
- executor->setRootReplacementExpression(expr);
-
- auto modifiedPaths = executor->getModifiedPaths();
- ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
-}
-
-TEST_F(SliceProjectionExecutionTest, CanApplySliceWithInclusionProjection) {
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{foo: [1,2]}")},
- applySlice(
- fromjson("{foo: 1}"), "foo", boost::none, 2, Document{fromjson("{foo: [1,2,6,10]}")}));
-
- ASSERT_DOCUMENT_EQ(Document{fromjson("{bar:1, foo: [6]}")},
- applySlice(fromjson("{bar: 1, foo: 1}"),
- "foo",
- 2,
- 1,
- Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
-}
-
-TEST_F(SliceProjectionExecutionTest, AppliesProjectionToPostImage) {
- ASSERT_DOCUMENT_EQ(Document{fromjson("{b: [1,2], c: 'abc'}")},
- applySlice(fromjson("{b: 1, c: 1}"),
- "b",
- boost::none,
- 2,
- Document{fromjson("{a: 1, b: [1,2,6,10], c: 'abc'}")}));
-}
-
-TEST_F(SliceProjectionExecutionTest, CanApplySliceAndPositionalProjectionsTogether) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{foo: 1, bar: 1}"), {});
- auto matchSpec = fromjson("{foo: {$gte: 3}}");
- auto matchExpr = CopyableMatchExpression{matchSpec,
- getExpCtx(),
- std::make_unique<ExtensionsCallbackNoop>(),
- MatchExpressionParser::kBanAllSpecialFeatures};
- auto positionalExpr = make_intrusive<ExpressionInternalFindPositional>(
- getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo",
- std::move(matchExpr));
- auto sliceExpr =
- make_intrusive<ExpressionInternalFindSlice>(getExpCtx(), positionalExpr, "bar", 1, 1);
- executor->setRootReplacementExpression(sliceExpr);
-
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{foo: [3], bar: [6]}")},
- executor->applyTransformation(Document{fromjson("{foo: [1,2,3,4], bar: [5,6,7,8]}")}));
-}
-
-TEST_F(SliceProjectionExecutionTest, CanApplySliceWithExclusionProjection) {
- ASSERT_DOCUMENT_EQ(
- Document{fromjson("{foo: [6]}")},
- applySlice(
- fromjson("{bar: 0}"), "foo", 2, 1, Document{fromjson("{bar: 1, foo: [1,2,6,10]}")}));
-}
-
-TEST_F(SliceProjectionExecutionTest,
- ShouldAddFieldsAndWholeDocumentToDependenciesWithInclusionProjection) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
- auto expr = make_intrusive<ExpressionInternalFindSlice>(
- getExpCtx(),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- 1,
- 1);
- executor->setRootReplacementExpression(expr);
-
- DepsTracker deps;
- executor->addDependencies(&deps);
-
- ASSERT_EQ(deps.fields.size(), 1UL);
- ASSERT_EQ(deps.fields.count("bar"), 1UL);
- ASSERT(deps.needWholeDocument);
-}
-
-TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithInclusionProjection) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 1}"), {});
- auto expr = make_intrusive<ExpressionInternalFindSlice>(
- getExpCtx(),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- 1,
- 1);
- executor->setRootReplacementExpression(expr);
-
- auto modifiedPaths = executor->getModifiedPaths();
- ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
-}
-
-TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithExclusionProjection) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 0}"), {});
- auto expr = make_intrusive<ExpressionInternalFindSlice>(
- getExpCtx(),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- 1,
- 1);
- executor->setRootReplacementExpression(expr);
-
- auto modifiedPaths = executor->getModifiedPaths();
- ASSERT(modifiedPaths.type == DocumentSource::GetModPathsReturn::Type::kAllPaths);
-}
-
-TEST_F(SliceProjectionExecutionTest, ShouldAddWholeDocumentToDependenciesWithExclusionProjection) {
- auto executor = createProjectionExecutor(getExpCtx(), fromjson("{bar: 0}"), {});
- auto expr = make_intrusive<ExpressionInternalFindSlice>(
- getExpCtx(),
- ExpressionFieldPath::parse(
- getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
- "foo.bar",
- 1,
- 1);
- executor->setRootReplacementExpression(expr);
-
- DepsTracker deps;
- executor->addDependencies(&deps);
-
- ASSERT_EQ(deps.fields.size(), 0UL);
- ASSERT(deps.needWholeDocument);
-}
-} // namespace mongo::parsed_aggregation_projection
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index a20a8382e4a..4f645f91dfa 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -67,7 +67,6 @@
#include "mongo/db/pipeline/document_source_sample_from_random_cursor.h"
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/document_source_sort.h"
-#include "mongo/db/pipeline/parsed_inclusion_projection.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/db/query/get_executor.h"
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index b39bf5d553d..876ab2b8c77 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -42,6 +42,7 @@ env.Library(
"$BUILD_DIR/mongo/base",
"$BUILD_DIR/mongo/db/bson/dotted_path_support",
'$BUILD_DIR/mongo/db/commands/server_status_core',
+ "$BUILD_DIR/mongo/db/exec/projection_executor",
"$BUILD_DIR/mongo/db/index/expression_params",
"$BUILD_DIR/mongo/db/index/key_generator",
"$BUILD_DIR/mongo/db/index_names",
diff --git a/src/mongo/db/query/collection_query_info.cpp b/src/mongo/db/query/collection_query_info.cpp
index b0e5685d7ad..ccf86e6a759 100644
--- a/src/mongo/db/query/collection_query_info.cpp
+++ b/src/mongo/db/query/collection_query_info.cpp
@@ -39,6 +39,8 @@
#include "mongo/db/catalog/index_catalog.h"
#include "mongo/db/concurrency/d_concurrency.h"
#include "mongo/db/curop_metrics.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_utils.h"
#include "mongo/db/fts/fts_spec.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/index/wildcard_access_method.h"
@@ -60,9 +62,9 @@ CoreIndexInfo indexInfoFromIndexCatalogEntry(const IndexCatalogEntry& ice) {
auto accessMethod = ice.accessMethod();
invariant(accessMethod);
- const ProjectionExecAgg* projExec = nullptr;
+ projection_executor::ProjectionExecutor* projExec = nullptr;
if (desc->getIndexType() == IndexType::INDEX_WILDCARD)
- projExec = static_cast<const WildcardAccessMethod*>(accessMethod)->getProjectionExec();
+ projExec = static_cast<const WildcardAccessMethod*>(accessMethod)->getProjectionExecutor();
return {desc->keyPattern(),
desc->getIndexType(),
@@ -102,15 +104,17 @@ void CollectionQueryInfo::computeIndexKeys(OperationContext* opCtx) {
if (descriptor->getAccessMethodName() == IndexNames::WILDCARD) {
// Obtain the projection used by the $** index's key generator.
const auto* pathProj =
- static_cast<const WildcardAccessMethod*>(iam)->getProjectionExec();
+ static_cast<const WildcardAccessMethod*>(iam)->getProjectionExecutor();
// If the projection is an exclusion, then we must check the new document's keys on all
// updates, since we do not exhaustively know the set of paths to be indexed.
- if (pathProj->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection) {
+ if (pathProj->getType() ==
+ TransformerInterface::TransformerType::kExclusionProjection) {
_indexedPaths.allPathsIndexed();
} else {
// If a subtree was specified in the keyPattern, or if an inclusion projection is
// present, then we need only index the path(s) preserved by the projection.
- for (const auto& path : pathProj->getExhaustivePaths()) {
+ for (const auto& path :
+ projection_executor_utils::extractExhaustivePaths(pathProj)) {
_indexedPaths.addPath(path);
}
}
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index e7f0513fbbc..df61af88f4e 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -48,6 +48,7 @@
#include "mongo/db/exec/idhack.h"
#include "mongo/db/exec/multi_plan.h"
#include "mongo/db/exec/projection.h"
+#include "mongo/db/exec/projection_executor_utils.h"
#include "mongo/db/exec/record_store_fast_count.h"
#include "mongo/db/exec/return_key.h"
#include "mongo/db/exec/shard_filter.h"
@@ -165,17 +166,18 @@ IndexEntry indexEntryFromIndexCatalogEntry(OperationContext* opCtx,
const bool isMultikey = desc->isMultikey();
- const ProjectionExecAgg* projExec = nullptr;
+ projection_executor::ProjectionExecutor* projExec = nullptr;
std::set<FieldRef> multikeyPathSet;
if (desc->getIndexType() == IndexType::INDEX_WILDCARD) {
- projExec = static_cast<const WildcardAccessMethod*>(accessMethod)->getProjectionExec();
+ projExec = static_cast<const WildcardAccessMethod*>(accessMethod)->getProjectionExecutor();
if (isMultikey) {
MultikeyMetadataAccessStats mkAccessStats;
if (canonicalQuery) {
stdx::unordered_set<std::string> fields;
QueryPlannerIXSelect::getFields(canonicalQuery->root(), &fields);
- const auto projectedFields = projExec->applyProjectionToFields(fields);
+ const auto projectedFields =
+ projection_executor_utils::applyProjectionToFields(projExec, fields);
multikeyPathSet =
accessMethod->getMultikeyPathSet(opCtx, projectedFields, &mkAccessStats);
@@ -1389,9 +1391,10 @@ QueryPlannerParams fillOutPlannerParamsForDistinct(OperationContext* opCtx,
indexEntryFromIndexCatalogEntry(opCtx, *ice, parsedDistinct.getQuery()));
} else if (desc->getIndexType() == IndexType::INDEX_WILDCARD && !query.isEmpty()) {
// Check whether the $** projection captures the field over which we are distinct-ing.
- const auto* proj =
- static_cast<const WildcardAccessMethod*>(ice->accessMethod())->getProjectionExec();
- if (proj->applyProjectionToOneField(parsedDistinct.getKey())) {
+ auto* proj = static_cast<const WildcardAccessMethod*>(ice->accessMethod())
+ ->getProjectionExecutor();
+ if (projection_executor_utils::applyProjectionToOneField(proj,
+ parsedDistinct.getKey())) {
plannerParams.indices.push_back(
indexEntryFromIndexCatalogEntry(opCtx, *ice, parsedDistinct.getQuery()));
}
diff --git a/src/mongo/db/query/get_executor_test.cpp b/src/mongo/db/query/get_executor_test.cpp
index d54080debef..9b36fcab408 100644
--- a/src/mongo/db/query/get_executor_test.cpp
+++ b/src/mongo/db/query/get_executor_test.cpp
@@ -37,7 +37,12 @@
#include <string>
#include "mongo/bson/simple_bsonobj_comparator.h"
+#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/json.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/db/query/projection_parser.h"
+#include "mongo/db/query/projection_policies.h"
#include "mongo/db/query/query_settings.h"
#include "mongo/db/query/query_test_service_context.h"
#include "mongo/stdx/unordered_set.h"
@@ -47,6 +52,13 @@
using namespace mongo;
namespace {
+auto createProjectionExecutor(const BSONObj& spec, const ProjectionPolicies& policies) {
+ const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto projection = projection_ast::parse(expCtx, spec, policies);
+ auto executor = projection_executor::buildProjectionExecutor(
+ expCtx, &projection, policies, true /* optimizeExecutor */);
+ return executor;
+}
using std::unique_ptr;
@@ -138,7 +150,7 @@ IndexEntry buildSimpleIndexEntry(const BSONObj& kp, const std::string& indexName
* is neccesary for wildcard indicies.
*/
IndexEntry buildWildcardIndexEntry(const BSONObj& kp,
- const ProjectionExecAgg* projExec,
+ projection_executor::ProjectionExecutor* projExec,
const std::string& indexName) {
return {kp,
IndexNames::nameToType(IndexNames::findPluginName(kp)),
@@ -207,10 +219,10 @@ TEST(GetExecutorTest, GetAllowedIndicesMatchesMultipleIndexesByKey) {
}
TEST(GetExecutorTest, GetAllowedWildcardIndicesByKey) {
- auto projExec = ProjectionExecAgg::create(
+ auto projExec = createProjectionExecutor(
fromjson("{_id: 0}"),
- ProjectionExecAgg::DefaultIdPolicy::kExcludeId,
- ProjectionExecAgg::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ {ProjectionPolicies::DefaultIdPolicy::kExcludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays});
testAllowedIndices({buildWildcardIndexEntry(BSON("$**" << 1), projExec.get(), "$**_1"),
buildSimpleIndexEntry(fromjson("{a: 1}"), "a_1"),
buildSimpleIndexEntry(fromjson("{a: 1, b: 1}"), "a_1_b_1"),
@@ -221,10 +233,10 @@ TEST(GetExecutorTest, GetAllowedWildcardIndicesByKey) {
}
TEST(GetExecutorTest, GetAllowedWildcardIndicesByName) {
- auto projExec = ProjectionExecAgg::create(
+ auto projExec = createProjectionExecutor(
fromjson("{_id: 0}"),
- ProjectionExecAgg::DefaultIdPolicy::kExcludeId,
- ProjectionExecAgg::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ {ProjectionPolicies::DefaultIdPolicy::kExcludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays});
testAllowedIndices({buildWildcardIndexEntry(BSON("$**" << 1), projExec.get(), "$**_1"),
buildSimpleIndexEntry(fromjson("{a: 1}"), "a_1"),
buildSimpleIndexEntry(fromjson("{a: 1, b: 1}"), "a_1_b_1"),
@@ -235,10 +247,10 @@ TEST(GetExecutorTest, GetAllowedWildcardIndicesByName) {
}
TEST(GetExecutorTest, GetAllowedPathSpecifiedWildcardIndicesByKey) {
- auto projExec = ProjectionExecAgg::create(
+ auto projExec = createProjectionExecutor(
fromjson("{_id: 0}"),
- ProjectionExecAgg::DefaultIdPolicy::kExcludeId,
- ProjectionExecAgg::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ {ProjectionPolicies::DefaultIdPolicy::kExcludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays});
testAllowedIndices({buildWildcardIndexEntry(BSON("a.$**" << 1), projExec.get(), "a.$**_1"),
buildSimpleIndexEntry(fromjson("{a: 1}"), "a_1"),
buildSimpleIndexEntry(fromjson("{a: 1, b: 1}"), "a_1_b_1"),
@@ -249,10 +261,10 @@ TEST(GetExecutorTest, GetAllowedPathSpecifiedWildcardIndicesByKey) {
}
TEST(GetExecutorTest, GetAllowedPathSpecifiedWildcardIndicesByName) {
- auto projExec = ProjectionExecAgg::create(
+ auto projExec = createProjectionExecutor(
fromjson("{_id: 0}"),
- ProjectionExecAgg::DefaultIdPolicy::kExcludeId,
- ProjectionExecAgg::ArrayRecursionPolicy::kDoNotRecurseNestedArrays);
+ {ProjectionPolicies::DefaultIdPolicy::kExcludeId,
+ ProjectionPolicies::ArrayRecursionPolicy::kDoNotRecurseNestedArrays});
testAllowedIndices({buildWildcardIndexEntry(BSON("a.$**" << 1), projExec.get(), "a.$**_1"),
buildSimpleIndexEntry(fromjson("{a: 1}"), "a_1"),
buildSimpleIndexEntry(fromjson("{a: 1, b: 1}"), "a_1_b_1"),
diff --git a/src/mongo/db/query/index_entry.h b/src/mongo/db/query/index_entry.h
index efe9179bc1d..d9eaf0ecdd7 100644
--- a/src/mongo/db/query/index_entry.h
+++ b/src/mongo/db/query/index_entry.h
@@ -32,7 +32,6 @@
#include <set>
#include <string>
-#include "mongo/db/exec/projection_exec_agg.h"
#include "mongo/db/field_ref.h"
#include "mongo/db/index/multikey_paths.h"
#include "mongo/db/index_names.h"
@@ -41,9 +40,11 @@
#include "mongo/util/str.h"
namespace mongo {
-
class CollatorInterface;
class MatchExpression;
+namespace projection_executor {
+class ProjectionExecutor;
+}
/**
* A CoreIndexInfo is a representation of an index in the catalog with parsed information which is
@@ -60,7 +61,7 @@ struct CoreIndexInfo {
Identifier ident,
const MatchExpression* fe = nullptr,
const CollatorInterface* ci = nullptr,
- const ProjectionExecAgg* projExec = nullptr)
+ projection_executor::ProjectionExecutor* projExec = nullptr)
: identifier(std::move(ident)),
keyPattern(kp),
filterExpr(fe),
@@ -137,7 +138,7 @@ struct CoreIndexInfo {
// For $** indexes, a pointer to the projection executor owned by the index access method. Null
// unless this IndexEntry represents a wildcard index, in which case this is always non-null.
- const ProjectionExecAgg* wildcardProjection = nullptr;
+ projection_executor::ProjectionExecutor* wildcardProjection = nullptr;
};
/**
@@ -159,7 +160,7 @@ struct IndexEntry : CoreIndexInfo {
const MatchExpression* fe,
const BSONObj& io,
const CollatorInterface* ci,
- const ProjectionExecAgg* projExec)
+ projection_executor::ProjectionExecutor* projExec)
: CoreIndexInfo(kp, type, sp, std::move(ident), fe, ci, projExec),
multikey(mk),
multikeyPaths(mkp),
diff --git a/src/mongo/db/query/plan_cache_indexability.cpp b/src/mongo/db/query/plan_cache_indexability.cpp
index 71d1fa456ce..96f7697e06c 100644
--- a/src/mongo/db/query/plan_cache_indexability.cpp
+++ b/src/mongo/db/query/plan_cache_indexability.cpp
@@ -35,6 +35,7 @@
#include "mongo/base/init.h"
#include "mongo/base/owned_pointer_vector.h"
+#include "mongo/db/exec/projection_executor_utils.h"
#include "mongo/db/index/wildcard_key_generator.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_algo.h"
@@ -148,7 +149,8 @@ IndexToDiscriminatorMap PlanCacheIndexabilityState::buildWildcardDiscriminators(
IndexToDiscriminatorMap ret;
for (auto&& wildcardDiscriminator : _wildcardIndexDiscriminators) {
- if (wildcardDiscriminator.projectionExec->applyProjectionToOneField(path)) {
+ if (projection_executor_utils::applyProjectionToOneField(
+ wildcardDiscriminator.projectionExec, path)) {
CompositeIndexabilityDiscriminator& cid = ret[wildcardDiscriminator.catalogName];
// We can use these 'shallow' functions because the code building the plan cache key
diff --git a/src/mongo/db/query/plan_cache_indexability.h b/src/mongo/db/query/plan_cache_indexability.h
index 426cec2fb25..e98e7101fc8 100644
--- a/src/mongo/db/query/plan_cache_indexability.h
+++ b/src/mongo/db/query/plan_cache_indexability.h
@@ -32,16 +32,17 @@
#include <functional>
#include <vector>
-#include "mongo/db/exec/projection_exec_agg.h"
#include "mongo/util/string_map.h"
namespace mongo {
-
class BSONObj;
class CollatorInterface;
class CompositeIndexabilityDiscriminator;
class MatchExpression;
struct CoreIndexInfo;
+namespace projection_executor {
+class ProjectionExecutor;
+}
using IndexabilityDiscriminator = std::function<bool(const MatchExpression* me)>;
using IndexabilityDiscriminators = std::vector<IndexabilityDiscriminator>;
@@ -116,7 +117,7 @@ private:
* index.
*/
struct WildcardIndexDiscriminatorContext {
- WildcardIndexDiscriminatorContext(const ProjectionExecAgg* proj,
+ WildcardIndexDiscriminatorContext(projection_executor::ProjectionExecutor* proj,
std::string name,
const MatchExpression* filter,
const CollatorInterface* coll)
@@ -126,7 +127,7 @@ private:
catalogName(std::move(name)) {}
// These are owned by the catalog.
- const ProjectionExecAgg* projectionExec;
+ projection_executor::ProjectionExecutor* projectionExec;
const MatchExpression* filterExpr; // For partial indexes.
const CollatorInterface* collator;
diff --git a/src/mongo/db/query/plan_cache_indexability_test.cpp b/src/mongo/db/query/plan_cache_indexability_test.cpp
index 48116f58416..eb2978ef6db 100644
--- a/src/mongo/db/query/plan_cache_indexability_test.cpp
+++ b/src/mongo/db/query/plan_cache_indexability_test.cpp
@@ -53,12 +53,12 @@ std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj,
return std::move(status.getValue());
}
-// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecAgg.
-// The latter simulates the ProjectionExecAgg which, during normal operation, is owned and
+// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecutor.
+// The latter simulates the ProjectionExecutor which, during normal operation, is owned and
// maintained by the $** index's IndexAccessMethod, and is required because the plan cache will
// obtain unowned pointers to it.
auto makeWildcardEntry(BSONObj keyPattern, const MatchExpression* filterExpr = nullptr) {
- auto projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, {});
+ auto projExec = WildcardKeyGenerator::createProjectionExecutor(keyPattern, {});
return std::make_pair(IndexEntry(keyPattern,
IndexNames::nameToType(IndexNames::findPluginName(keyPattern)),
false, // multikey
diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp
index a4ee72710d8..ce580870ca9 100644
--- a/src/mongo/db/query/plan_cache_test.cpp
+++ b/src/mongo/db/query/plan_cache_test.cpp
@@ -239,12 +239,13 @@ void assertEquivalent(const char* queryStr,
FAIL(ss);
}
-// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecAgg.
-// The latter simulates the ProjectionExecAgg which, during normal operation, is owned and
+// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecutor.
+// The latter simulates the ProjectionExecutor which, during normal operation, is owned and
// maintained by the $** index's IndexAccessMethod, and is required because the plan cache will
// obtain unowned pointers to it.
-std::pair<IndexEntry, std::unique_ptr<ProjectionExecAgg>> makeWildcardEntry(BSONObj keyPattern) {
- auto projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, {});
+std::pair<IndexEntry, std::unique_ptr<projection_executor::ProjectionExecutor>> makeWildcardEntry(
+ BSONObj keyPattern) {
+ auto projExec = WildcardKeyGenerator::createProjectionExecutor(keyPattern, {});
return {IndexEntry(keyPattern,
IndexNames::nameToType(IndexNames::findPluginName(keyPattern)),
false, // multikey
@@ -261,9 +262,9 @@ std::pair<IndexEntry, std::unique_ptr<ProjectionExecAgg>> makeWildcardEntry(BSON
}
// A version of the above for CoreIndexInfo, used for plan cache update tests.
-std::pair<CoreIndexInfo, std::unique_ptr<ProjectionExecAgg>> makeWildcardUpdate(
- BSONObj keyPattern) {
- auto projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, {});
+std::pair<CoreIndexInfo, std::unique_ptr<projection_executor::ProjectionExecutor>>
+makeWildcardUpdate(BSONObj keyPattern) {
+ auto projExec = WildcardKeyGenerator::createProjectionExecutor(keyPattern, {});
return {CoreIndexInfo(keyPattern,
IndexNames::nameToType(IndexNames::findPluginName(keyPattern)),
false, // sparse
diff --git a/src/mongo/db/query/planner_ixselect_test.cpp b/src/mongo/db/query/planner_ixselect_test.cpp
index e1018a87944..406104b96b0 100644
--- a/src/mongo/db/query/planner_ixselect_test.cpp
+++ b/src/mongo/db/query/planner_ixselect_test.cpp
@@ -1050,15 +1050,15 @@ TEST(QueryPlannerIXSelectTest, NoStringComparisonType) {
}
}
-// Helper which constructs an IndexEntry and returns it along with an owned ProjectionExecAgg, which
-// is non-null if the requested entry represents a wildcard index and null otherwise. When non-null,
-// it simulates the ProjectionExecAgg that is owned by the $** IndexAccessMethod.
+// Helper which constructs an IndexEntry and returns it along with an owned ProjectionExecutor,
+// which is non-null if the requested entry represents a wildcard index and null otherwise. When
+// non-null, it simulates the ProjectionExecutor that is owned by the $** IndexAccessMethod.
auto makeIndexEntry(BSONObj keyPattern,
MultikeyPaths multiKeyPaths,
std::set<FieldRef> multiKeyPathSet = {},
BSONObj infoObj = BSONObj()) {
auto projExec = (keyPattern.firstElement().fieldNameStringData().endsWith("$**"_sd)
- ? WildcardKeyGenerator::createProjectionExec(
+ ? WildcardKeyGenerator::createProjectionExecutor(
keyPattern, infoObj.getObjectField("wildcardProjection"))
: nullptr);
diff --git a/src/mongo/db/query/planner_wildcard_helpers.cpp b/src/mongo/db/query/planner_wildcard_helpers.cpp
index 43f4ab69bb3..edf768c75a3 100644
--- a/src/mongo/db/query/planner_wildcard_helpers.cpp
+++ b/src/mongo/db/query/planner_wildcard_helpers.cpp
@@ -36,6 +36,7 @@
#include <vector>
#include "mongo/bson/util/builder.h"
+#include "mongo/db/exec/projection_executor_utils.h"
#include "mongo/db/index/wildcard_key_generator.h"
#include "mongo/db/query/index_bounds.h"
#include "mongo/util/log.h"
@@ -355,13 +356,12 @@ void expandWildcardIndexEntry(const IndexEntry& wildcardIndex,
invariant(wildcardIndex.multikeyPaths.empty());
// Obtain the projection executor from the parent wildcard IndexEntry.
- const auto* projExec = wildcardIndex.wildcardProjection;
+ auto projExec = wildcardIndex.wildcardProjection;
invariant(projExec);
- const auto projectedFields = projExec->applyProjectionToFields(fields);
-
- const auto& includedPaths = projExec->getExhaustivePaths();
-
+ const auto projectedFields =
+ projection_executor_utils::applyProjectionToFields(projExec, fields);
+ const auto& includedPaths = projection_executor_utils::extractExhaustivePaths(projExec);
out->reserve(out->size() + projectedFields.size());
for (auto&& fieldName : projectedFields) {
// Convert string 'fieldName' into a FieldRef, to better facilitate the subsequent checks.
diff --git a/src/mongo/db/query/projection_policies.h b/src/mongo/db/query/projection_policies.h
index 4d2d6a54fba..09094227c86 100644
--- a/src/mongo/db/query/projection_policies.h
+++ b/src/mongo/db/query/projection_policies.h
@@ -64,18 +64,24 @@ struct ProjectionPolicies {
FindOnlyFeaturesPolicy::kBanFindOnlyFeatures;
static ProjectionPolicies findProjectionPolicies() {
- return ProjectionPolicies{
- ProjectionPolicies::kDefaultIdPolicyDefault,
- ProjectionPolicies::kArrayRecursionPolicyDefault,
- ProjectionPolicies::kComputedFieldsPolicyDefault,
- ProjectionPolicies::FindOnlyFeaturesPolicy::kAllowFindOnlyFeatures};
+ return ProjectionPolicies{kDefaultIdPolicyDefault,
+ kArrayRecursionPolicyDefault,
+ kComputedFieldsPolicyDefault,
+ FindOnlyFeaturesPolicy::kAllowFindOnlyFeatures};
}
static ProjectionPolicies aggregateProjectionPolicies() {
- return ProjectionPolicies{ProjectionPolicies::kDefaultIdPolicyDefault,
- ProjectionPolicies::kArrayRecursionPolicyDefault,
- ProjectionPolicies::kComputedFieldsPolicyDefault,
- ProjectionPolicies::FindOnlyFeaturesPolicy::kBanFindOnlyFeatures};
+ return ProjectionPolicies{kDefaultIdPolicyDefault,
+ kArrayRecursionPolicyDefault,
+ kComputedFieldsPolicyDefault,
+ FindOnlyFeaturesPolicy::kBanFindOnlyFeatures};
+ }
+
+ static ProjectionPolicies wildcardIndexSpecProjectionPolicies() {
+ return ProjectionPolicies{DefaultIdPolicy::kExcludeId,
+ ArrayRecursionPolicy::kDoNotRecurseNestedArrays,
+ ComputedFieldsPolicy::kBanComputedFields,
+ FindOnlyFeaturesPolicy::kBanFindOnlyFeatures};
}
ProjectionPolicies(
diff --git a/src/mongo/db/query/query_planner_wildcard_index_test.cpp b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
index 318d43728e9..4044262b10a 100644
--- a/src/mongo/db/query/query_planner_wildcard_index_test.cpp
+++ b/src/mongo/db/query/query_planner_wildcard_index_test.cpp
@@ -71,7 +71,7 @@ protected:
const bool isMultikey = !multikeyPathSet.empty();
BSONObj infoObj = BSON("wildcardProjection" << wildcardProjection);
- _projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, wildcardProjection);
+ _projExec = WildcardKeyGenerator::createProjectionExecutor(keyPattern, wildcardProjection);
params.indices.push_back({std::move(keyPattern),
IndexType::INDEX_WILDCARD,
@@ -87,7 +87,7 @@ protected:
_projExec.get()});
}
- std::unique_ptr<ProjectionExecAgg> _projExec;
+ std::unique_ptr<projection_executor::ProjectionExecutor> _projExec;
};
//
diff --git a/src/mongo/embedded/stitch_support/stitch_support.cpp b/src/mongo/embedded/stitch_support/stitch_support.cpp
index 0613899cfdc..8aa464be738 100644
--- a/src/mongo/embedded/stitch_support/stitch_support.cpp
+++ b/src/mongo/embedded/stitch_support/stitch_support.cpp
@@ -36,6 +36,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/db/client.h"
#include "mongo/db/exec/projection_executor.h"
+#include "mongo/db/exec/projection_executor_builder.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/db/ops/parsed_update.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
@@ -202,8 +203,7 @@ struct stitch_support_v1_projection {
mongo::ServiceContext::UniqueClient client;
mongo::ServiceContext::UniqueOperationContext opCtx;
- std::unique_ptr<mongo::parsed_aggregation_projection::ParsedAggregationProjection>
- projectionExec;
+ std::unique_ptr<mongo::projection_executor::ProjectionExecutor> projectionExec;
bool requiresMatch = false;
stitch_support_v1_matcher* matcher;