summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAnton Korshunov <anton.korshunov@mongodb.com>2019-09-24 18:09:14 +0000
committerevergreen <evergreen@mongodb.com>2019-09-24 18:09:14 +0000
commitd4c306ea210d905578c2cf464a117815d6ea83b9 (patch)
tree761704b51925421326e8975625f22f9c07e0481a /src/mongo
parent8e35b7bc673e3bddf7049a44884d79ffb862a77d (diff)
downloadmongo-d4c306ea210d905578c2cf464a117815d6ea83b9.tar.gz
SERVER-43377 Make positional projection internal expression able to access Document pre-image
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/exec/find_projection_executor.cpp9
-rw-r--r--src/mongo/db/exec/find_projection_executor.h21
-rw-r--r--src/mongo/db/exec/find_projection_executor_test.cpp22
-rw-r--r--src/mongo/db/pipeline/expression_find_internal.h23
-rw-r--r--src/mongo/db/pipeline/expression_find_internal_test.cpp52
-rw-r--r--src/mongo/db/pipeline/parsed_aggregation_projection.h24
-rw-r--r--src/mongo/db/pipeline/parsed_find_projection_test.cpp43
7 files changed, 157 insertions, 37 deletions
diff --git a/src/mongo/db/exec/find_projection_executor.cpp b/src/mongo/db/exec/find_projection_executor.cpp
index a1855ba7e8a..098cdb93d71 100644
--- a/src/mongo/db/exec/find_projection_executor.cpp
+++ b/src/mongo/db/exec/find_projection_executor.cpp
@@ -156,10 +156,11 @@ Value extractArrayElement(const Value& arr, const std::string& elemIndex) {
return arr[*index];
}
-Document applyPositionalProjection(const Document& input,
+Document applyPositionalProjection(const Document& preImage,
+ const Document& postImage,
const MatchExpression& matchExpr,
const FieldPath& path) {
- MutableDocument output(input);
+ MutableDocument output(postImage);
// Try to find the first matching array element from the 'input' document based on the condition
// specified as 'matchExpr'. If such an element is found, its position within an array will be
@@ -169,7 +170,7 @@ Document applyPositionalProjection(const Document& input,
// invariant to make sure this is the case indeed.
MatchDetails details;
details.requestElemMatchKey();
- invariant(matchExpr.matchesBSON(input.toBson(), &details));
+ invariant(matchExpr.matchesBSON(preImage.toBson(), &details));
// At this stage we know that the 'input' document matches against the specified condition,
// but the matching array element may not be found. This can happen if the field, specified
@@ -181,7 +182,7 @@ Document applyPositionalProjection(const Document& input,
// found, then we will extract the matching element from this array and will store it as
// the current sub-path in the 'output' document. Otherwise, just leave the 'output'
// document untouched.
- for (auto [ind, subDoc] = std::pair{0ULL, input}; ind < path.getPathLength(); ind++) {
+ for (auto [ind, subDoc] = std::pair{0ULL, postImage}; ind < path.getPathLength(); ind++) {
switch (auto val = subDoc[path.getFieldName(ind)]; val.getType()) {
case BSONType::Array: {
// Raise an error if we found the first array on the 'path', but the matching array
diff --git a/src/mongo/db/exec/find_projection_executor.h b/src/mongo/db/exec/find_projection_executor.h
index 0add0908de1..c14e63f7386 100644
--- a/src/mongo/db/exec/find_projection_executor.h
+++ b/src/mongo/db/exec/find_projection_executor.h
@@ -43,21 +43,28 @@ namespace projection_executor {
Value extractArrayElement(const Value& arr, const std::string& elemIndex);
/**
- * Applies a positional projection on the first array found in the 'path' on the 'input' document.
- * The applied projection is returned as a Document. The 'matchExpr' specifies a condition to
- * locate the first matching element in the array and must match the input document. For example,
- * given:
+ * Applies a positional projection on the first array found in the 'path' on a projection
+ * 'preImage' document. The applied projection is merged with a projection 'postImage' document.
+ * The 'matchExpr' specifies a condition to locate the first matching element in the array and must
+ * match the input document. Note that the match expression must be applied to the projection
+ * post-image, as it may contain conditions on fields which are not included into the projection
+ * post-image. So, the pre-image document will be used to match an array and record a position of
+ * the matching element, whilst the actual result will be merged into the post-image.
*
- * - the 'input' document {bar: 1, foo: {bar: [1,2,6,10]}}
+ * For example, given:
+ *
+ * - the 'preImage' document {bar: 1, foo: {bar: [1,2,6,10]}}
+ * - the 'postImage' document {foo: {bar: [1,2,6,10]}}
* - the 'matchExpr' condition {bar: 1, 'foo.bar': {$gte: 5}}
* - and the 'path' for the positional projection of 'foo.bar'
*
- * The resulting document will contain the following element: {bar: 1, foo: {bar: [6]}}
+ * The resulting document will contain the following element: {foo: {bar: [6]}}
*
* Throws an AssertionException if 'matchExpr' matches the input document, but an array element
* satisfying positional projection requirements cannot be found.
*/
-Document applyPositionalProjection(const Document& input,
+Document applyPositionalProjection(const Document& preImage,
+ const Document& postImage,
const MatchExpression& matchExpr,
const FieldPath& path);
/**
diff --git a/src/mongo/db/exec/find_projection_executor_test.cpp b/src/mongo/db/exec/find_projection_executor_test.cpp
index 32654ca050f..0282d8ca383 100644
--- a/src/mongo/db/exec/find_projection_executor_test.cpp
+++ b/src/mongo/db/exec/find_projection_executor_test.cpp
@@ -39,10 +39,19 @@
namespace mongo {
namespace projection_executor {
namespace positional_projection_tests {
-auto applyPositional(const BSONObj& match, const std::string& path, const Document& input) {
+/**
+ * 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(match, expCtx));
- return projection_executor::applyPositionalProjection(input, *matchExpr, path);
+ auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, expCtx));
+ return projection_executor::applyPositionalProjection(
+ preImage, postImage.value_or(preImage), *matchExpr, path);
}
TEST(PositionalProjection, CorrectlyProjectsSimplePath) {
@@ -115,6 +124,13 @@ TEST(PositionalProjection, CanMergeWithExistingFieldsInOutputDocument) {
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 {
diff --git a/src/mongo/db/pipeline/expression_find_internal.h b/src/mongo/db/pipeline/expression_find_internal.h
index 2459bcf14cb..61a696c5d9d 100644
--- a/src/mongo/db/pipeline/expression_find_internal.h
+++ b/src/mongo/db/pipeline/expression_find_internal.h
@@ -44,21 +44,29 @@ namespace mongo {
class ExpressionInternalFindPositional final : public Expression {
public:
ExpressionInternalFindPositional(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- boost::intrusive_ptr<Expression> child,
+ boost::intrusive_ptr<Expression> preImageExpr,
+ boost::intrusive_ptr<Expression> postImageExpr,
FieldPath path,
const MatchExpression* matchExpr)
- : Expression{expCtx, {child}}, _path{std::move(path)}, _matchExpr{matchExpr} {}
+ : Expression{expCtx, {preImageExpr, postImageExpr}},
+ _path{std::move(path)},
+ _matchExpr{matchExpr} {}
Value evaluate(const Document& root, Variables* variables) const final {
using namespace fmt::literals;
- auto postImage = _children[0]->evaluate(root, variables);
+ auto preImage = _children[0]->evaluate(root, variables);
+ auto postImage = _children[1]->evaluate(root, variables);
uassert(51255,
- "Positional operator can only be applied to an object, but got {}"_format(
+ "Positional operator pre-image can only be an object, but got {}"_format(
+ typeName(preImage.getType())),
+ preImage.getType() == BSONType::Object);
+ uassert(51258,
+ "Positional operator post-image can only be an object, but got {}"_format(
typeName(postImage.getType())),
postImage.getType() == BSONType::Object);
return Value{projection_executor::applyPositionalProjection(
- postImage.getDocument(), *_matchExpr, _path)};
+ preImage.getDocument(), postImage.getDocument(), *_matchExpr, _path)};
}
void acceptVisitor(ExpressionVisitor* visitor) final {
@@ -79,6 +87,7 @@ public:
protected:
void _doAddDependencies(DepsTracker* deps) const final {
_children[0]->addDependencies(deps);
+ _children[1]->addDependencies(deps);
deps->needWholeDocument = true;
_matchExpr->addDependencies(deps);
}
@@ -97,11 +106,11 @@ private:
class ExpressionInternalFindSlice final : public Expression {
public:
ExpressionInternalFindSlice(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- boost::intrusive_ptr<Expression> child,
+ boost::intrusive_ptr<Expression> postImageExpr,
FieldPath path,
boost::optional<int> skip,
int limit)
- : Expression{expCtx, {child}}, _path{std::move(path)}, _skip{skip}, _limit{limit} {}
+ : Expression{expCtx, {postImageExpr}}, _path{std::move(path)}, _skip{skip}, _limit{limit} {}
Value evaluate(const Document& root, Variables* variables) const final {
using namespace fmt::literals;
diff --git a/src/mongo/db/pipeline/expression_find_internal_test.cpp b/src/mongo/db/pipeline/expression_find_internal_test.cpp
index 453a37c7055..9fff474201c 100644
--- a/src/mongo/db/pipeline/expression_find_internal_test.cpp
+++ b/src/mongo/db/pipeline/expression_find_internal_test.cpp
@@ -36,12 +36,25 @@
#include "mongo/unittest/unittest.h"
namespace mongo::expression_internal_tests {
+constexpr auto kProjectionPostImageVarName =
+ parsed_aggregation_projection::ParsedAggregationProjection::kProjectionPostImageVarName;
+
+auto defineAndSetProjectionPostImageVariable(boost::intrusive_ptr<ExpressionContext> expCtx,
+ Value postImage) {
+ auto& vps = expCtx->variablesParseState;
+ auto varId = vps.defineVariable(kProjectionPostImageVarName);
+ expCtx->variables.setValue(varId, postImage);
+ return varId;
+}
+
class ExpressionInternalFindPositionalTest : public AggregationContextFixture {
protected:
auto createExpression(const MatchExpression* matchExpr, const std::string& path) {
auto expr = make_intrusive<ExpressionInternalFindPositional>(
getExpCtx(),
ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
path,
matchExpr);
return expr;
@@ -53,7 +66,8 @@ protected:
auto createExpression(const std::string& path, boost::optional<int> skip, int limit) {
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
path,
skip,
limit);
@@ -72,7 +86,10 @@ protected:
}
};
-TEST_F(ExpressionInternalFindPositionalTest, AppliesProjectionToRootDocument) {
+TEST_F(ExpressionInternalFindPositionalTest, AppliesProjectionToPostImage) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto matchSpec = fromjson("{bar: 1, foo: {$gte: 5}}");
auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, getExpCtx()));
auto expr = createExpression(matchExpr.get(), "foo");
@@ -84,6 +101,8 @@ TEST_F(ExpressionInternalFindPositionalTest, AppliesProjectionToRootDocument) {
}
TEST_F(ExpressionInternalFindPositionalTest, RecordsProjectionDependencies) {
+ auto varId = defineAndSetProjectionPostImageVariable(
+ getExpCtx(), Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
auto matchSpec = fromjson("{bar: 1, foo: {$gte: 5}}");
auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, getExpCtx()));
auto expr = createExpression(matchExpr.get(), "foo");
@@ -94,11 +113,15 @@ TEST_F(ExpressionInternalFindPositionalTest, RecordsProjectionDependencies) {
ASSERT_EQ(deps.fields.size(), 2UL);
ASSERT_EQ(deps.fields.count("bar"), 1UL);
ASSERT_EQ(deps.fields.count("foo"), 1UL);
- ASSERT_EQ(deps.vars.size(), 0UL);
- ASSERT(deps.needWholeDocument);
+ ASSERT_EQ(deps.vars.size(), 1UL);
+ ASSERT_EQ(deps.vars.count(varId), 1UL);
+ ASSERT_TRUE(deps.needWholeDocument);
}
TEST_F(ExpressionInternalFindPositionalTest, AddsArrayUndottedPathToComputedPaths) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto matchSpec = fromjson("{bar: 1, foo: {$gte: 5}}");
auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, getExpCtx()));
auto expr = createExpression(matchExpr.get(), "foo");
@@ -113,6 +136,9 @@ TEST_F(ExpressionInternalFindPositionalTest, AddsArrayUndottedPathToComputedPath
TEST_F(ExpressionInternalFindPositionalTest,
AddsOnlyTopLevelFieldOfArrayDottedPathToComputedPaths) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto matchSpec = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
auto matchExpr = uassertStatusOK(MatchExpressionParser::parse(matchSpec, getExpCtx()));
auto expr = createExpression(matchExpr.get(), "foo.bar");
@@ -125,7 +151,10 @@ TEST_F(ExpressionInternalFindPositionalTest,
ASSERT_EQ(computedPaths.paths.count("foo"), 1UL);
}
-TEST_F(ExpressionInternalFindSliceTest, AppliesProjectionToRootDocument) {
+TEST_F(ExpressionInternalFindSliceTest, AppliesProjectionToPostImage) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto expr = createExpression("foo", 1, 2);
ASSERT_DOCUMENT_EQ(
@@ -135,17 +164,23 @@ TEST_F(ExpressionInternalFindSliceTest, AppliesProjectionToRootDocument) {
}
TEST_F(ExpressionInternalFindSliceTest, RecordsProjectionDependencies) {
+ auto varId = defineAndSetProjectionPostImageVariable(
+ getExpCtx(), Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
auto expr = createExpression("foo", 1, 2);
DepsTracker deps;
expr->addDependencies(&deps);
ASSERT_EQ(deps.fields.size(), 0UL);
- ASSERT_EQ(deps.vars.size(), 0UL);
- ASSERT(deps.needWholeDocument);
+ ASSERT_EQ(deps.vars.size(), 1UL);
+ ASSERT_EQ(deps.vars.count(varId), 1UL);
+ ASSERT_TRUE(deps.needWholeDocument);
}
TEST_F(ExpressionInternalFindSliceTest, AddsArrayUndottedPathToComputedPaths) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto expr = createExpression("foo", 1, 2);
DepsTracker deps;
@@ -157,6 +192,9 @@ TEST_F(ExpressionInternalFindSliceTest, AddsArrayUndottedPathToComputedPaths) {
}
TEST_F(ExpressionInternalFindSliceTest, AddsTopLevelFieldOfArrayDottedPathToComputedPaths) {
+ defineAndSetProjectionPostImageVariable(getExpCtx(),
+ Value{fromjson("{bar: 1, foo: [1,2,6,10]}")});
+
auto expr = createExpression("foo.bar", 1, 2);
DepsTracker deps;
diff --git a/src/mongo/db/pipeline/parsed_aggregation_projection.h b/src/mongo/db/pipeline/parsed_aggregation_projection.h
index d1ee17056cd..e7ac7d2ab95 100644
--- a/src/mongo/db/pipeline/parsed_aggregation_projection.h
+++ b/src/mongo/db/pipeline/parsed_aggregation_projection.h
@@ -144,6 +144,12 @@ private:
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};
+
+ /**
* Main entry point for a ParsedAggregationProjection.
*
* Throws a AssertionException if 'spec' is an invalid projection specification.
@@ -185,7 +191,7 @@ public:
Document applyTransformation(const Document& input) {
auto output = applyProjection(input);
if (_rootReplacementExpression) {
- return _applyRootReplacementExpression(output);
+ return _applyRootReplacementExpression(input, output);
}
return output;
}
@@ -193,7 +199,8 @@ public:
/**
* 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 $$ROOT variable is this expression needs access to it.
+ * 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;
@@ -202,7 +209,10 @@ public:
protected:
ParsedAggregationProjection(const boost::intrusive_ptr<ExpressionContext>& expCtx,
ProjectionPolicies policies)
- : _expCtx(expCtx), _policies(policies) {}
+ : _expCtx(expCtx),
+ _policies(policies),
+ _projectionPostImageVarId{
+ _expCtx->variablesParseState.defineVariable(kProjectionPostImageVarName)} {}
/**
* Apply the projection to 'input'.
@@ -216,9 +226,10 @@ protected:
boost::intrusive_ptr<Expression> _rootReplacementExpression;
private:
- Document _applyRootReplacementExpression(const Document& input) {
+ 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(
@@ -226,6 +237,11 @@ private:
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_find_projection_test.cpp b/src/mongo/db/pipeline/parsed_find_projection_test.cpp
index e7ae148ad77..c07f800578a 100644
--- a/src/mongo/db/pipeline/parsed_find_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_find_projection_test.cpp
@@ -36,6 +36,9 @@
#include "mongo/unittest/unittest.h"
namespace mongo::parsed_aggregation_projection {
+constexpr auto kProjectionPostImageVarName =
+ parsed_aggregation_projection::ParsedAggregationProjection::kProjectionPostImageVarName;
+
class PositionalProjectionExecutionTest : public AggregationContextFixture {
protected:
auto applyPositional(const BSONObj& projSpec,
@@ -47,6 +50,8 @@ protected:
auto expr = make_intrusive<ExpressionInternalFindPositional>(
getExpCtx(),
ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
path,
matchExpr.get());
proj->setRootReplacementExpression(expr);
@@ -64,7 +69,8 @@ protected:
auto proj = ParsedAggregationProjection::create(getExpCtx(), projSpec, {});
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
path,
skip,
limit);
@@ -87,6 +93,14 @@ TEST_F(PositionalProjectionExecutionTest, CanApplyPositionalWithInclusionProject
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 proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
auto match = fromjson("{bar: 1, 'foo.bar': {$gte: 5}}");
@@ -94,6 +108,8 @@ TEST_F(PositionalProjectionExecutionTest, ShouldAddInclusionFieldsAndWholeDocume
auto expr = make_intrusive<ExpressionInternalFindPositional>(
getExpCtx(),
ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
matchExpr.get());
proj->setRootReplacementExpression(expr);
@@ -114,6 +130,8 @@ TEST_F(PositionalProjectionExecutionTest, ShouldConsiderAllPathsAsModified) {
auto expr = make_intrusive<ExpressionInternalFindPositional>(
getExpCtx(),
ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
matchExpr.get());
proj->setRootReplacementExpression(expr);
@@ -136,6 +154,15 @@ TEST_F(SliceProjectionExecutionTest, CanApplySliceWithInclusionProjection) {
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 proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{foo: 1, bar: 1}"), {});
auto matchSpec = fromjson("{foo: {$gte: 3}}");
@@ -143,6 +170,8 @@ TEST_F(SliceProjectionExecutionTest, CanApplySliceAndPositionalProjectionsTogeth
auto positionalExpr = make_intrusive<ExpressionInternalFindPositional>(
getExpCtx(),
ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo",
matchExpr.get());
auto sliceExpr =
@@ -166,7 +195,8 @@ TEST_F(SliceProjectionExecutionTest,
auto proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{bar: 1, _id: 0}"), {});
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
1,
1);
@@ -184,7 +214,8 @@ TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithInclusi
auto proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{bar: 1}"), {});
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
1,
1);
@@ -198,7 +229,8 @@ TEST_F(SliceProjectionExecutionTest, ShouldConsiderAllPathsAsModifiedWithExclusi
auto proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{bar: 0}"), {});
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
1,
1);
@@ -212,7 +244,8 @@ TEST_F(SliceProjectionExecutionTest, ShouldAddWholeDocumentToDependenciesWithExc
auto proj = ParsedAggregationProjection::create(getExpCtx(), fromjson("{bar: 0}"), {});
auto expr = make_intrusive<ExpressionInternalFindSlice>(
getExpCtx(),
- ExpressionFieldPath::parse(getExpCtx(), "$$ROOT", getExpCtx()->variablesParseState),
+ ExpressionFieldPath::parse(
+ getExpCtx(), "$$" + kProjectionPostImageVarName, getExpCtx()->variablesParseState),
"foo.bar",
1,
1);