summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Wahlin <james@mongodb.com>2017-09-28 12:21:30 -0400
committerJames Wahlin <james@mongodb.com>2017-10-10 16:39:06 -0400
commit2f3b96e636329b68809bc63b681a862e3d3bccd5 (patch)
tree93294474137e9607b145c3a9abab01dc84543110
parent0ef1d6ec05798f3dae9b838ad8a8fffdd7ec4990 (diff)
downloadmongo-2f3b96e636329b68809bc63b681a862e3d3bccd5.tar.gz
SERVER-30989 Add Expression rewrite to ExprMatchExpression
-rw-r--r--jstests/aggregation/sources/match/expr_index_use.js163
-rw-r--r--src/mongo/db/matcher/expression_expr.cpp36
-rw-r--r--src/mongo/db/matcher/expression_expr.h7
-rw-r--r--src/mongo/db/matcher/expression_expr_test.cpp506
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp3
-rw-r--r--src/mongo/db/matcher/rewrite_expr.cpp12
-rw-r--r--src/mongo/db/matcher/rewrite_expr.h12
-rw-r--r--src/mongo/db/matcher/rewrite_expr_test.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_lookup_test.cpp3
9 files changed, 668 insertions, 84 deletions
diff --git a/jstests/aggregation/sources/match/expr_index_use.js b/jstests/aggregation/sources/match/expr_index_use.js
new file mode 100644
index 00000000000..0d3eecd2a10
--- /dev/null
+++ b/jstests/aggregation/sources/match/expr_index_use.js
@@ -0,0 +1,163 @@
+// Confirms expected index use when performing a match with a $expr statement.
+
+(function() {
+ "use strict";
+
+ load("jstests/libs/analyze_plan.js");
+
+ const coll = db.expr_index_use;
+ coll.drop();
+
+ assert.writeOK(coll.insert({x: 0}));
+ assert.writeOK(coll.insert({x: 1, y: 1}));
+ assert.writeOK(coll.insert({x: 2, y: 2}));
+ assert.writeOK(coll.insert({x: 3, y: 10}));
+ assert.writeOK(coll.insert({y: 20}));
+ assert.writeOK(coll.insert({a: {b: 1}}));
+ assert.writeOK(coll.insert({a: {b: [1]}}));
+ assert.writeOK(coll.insert({a: [{b: 1}]}));
+ assert.writeOK(coll.insert({a: [{b: [1]}]}));
+
+ assert.commandWorked(coll.createIndex({x: 1, y: 1}));
+ assert.commandWorked(coll.createIndex({y: 1}));
+ assert.commandWorked(coll.createIndex({"a.b": 1}));
+
+ /**
+ * Executes an 'executionStats' explain on pipeline and confirms 'metricsToCheck' which is an
+ * object containing:
+ * - nReturned: The number of documents the pipeline is expected to return.
+ * - expectedIndex: Either an index specification object when index use is expected or
+ * 'null' if a collection scan is expected.
+ * - indexBounds: The expected index bounds.
+ */
+ function confirmExpectedPipelineExecution(pipeline, metricsToCheck) {
+ assert(metricsToCheck.hasOwnProperty("nReturned"),
+ "metricsToCheck must contain an nReturned field");
+
+ assert.eq(metricsToCheck.nReturned, coll.aggregate(pipeline).itcount());
+
+ const explain = assert.commandWorked(coll.explain("executionStats").aggregate(pipeline));
+ if (metricsToCheck.hasOwnProperty("expectedIndex")) {
+ const stage = getAggPlanStage(explain, "IXSCAN");
+ assert.neq(null, stage, tojson(explain));
+ assert(stage.hasOwnProperty("keyPattern"), tojson(explain));
+ assert.docEq(stage.keyPattern, metricsToCheck.expectedIndex, tojson(explain));
+ } else {
+ assert.neq(null, aggPlanHasStage(explain, "COLLSCAN"), tojson(explain));
+ }
+
+ if (metricsToCheck.hasOwnProperty("indexBounds")) {
+ const stage = getAggPlanStage(explain, "IXSCAN");
+ assert.neq(null, stage, tojson(explain));
+ assert(stage.hasOwnProperty("indexBounds"), tojson(explain));
+ assert.docEq(stage.indexBounds, metricsToCheck.indexBounds, tojson(explain));
+ }
+ }
+
+ // Comparison of field and constant.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$x", 1]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[1.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$eq: [1, "$x"]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[1.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$lt: ["$x", 1]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[-inf.0, 1.0)"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$lt: [1, "$x"]}}}], {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$lte: ["$x", 1]}}}], {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[-inf.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$lte: [1, "$x"]}}}], {
+ nReturned: 3,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$gt: ["$x", 1]}}}], {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$gt: [1, "$x"]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[-inf.0, 1.0)"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$gte: ["$x", 1]}}}], {
+ nReturned: 3,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+ confirmExpectedPipelineExecution([{$match: {$expr: {$gte: [1, "$x"]}}}], {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[-inf.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+
+ // $in with field and array of values.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$in: ["$x", [1, 3]]}}}], {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[1.0, 1.0]", "[3.0, 3.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+
+ // $and with both children eligible for index use.
+ confirmExpectedPipelineExecution(
+ [{$match: {$expr: {$and: [{$eq: ["$x", 2]}, {$gt: ["$y", 1]}]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["[2.0, 2.0]"], "y": ["(1.0, inf.0]"]}
+ });
+
+ // $and with one child eligible for index use and one that is not.
+ confirmExpectedPipelineExecution(
+ [{$match: {$expr: {$and: [{$gt: ["$x", 1]}, {$eq: ["$x", "$y"]}]}}}], {
+ nReturned: 1,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+
+ // $and with one child elibible for index use and a second child containing a $or where one of
+ // the two children are eligible.
+ confirmExpectedPipelineExecution(
+ [
+ {
+ $match: {
+ $expr:
+ {$and: [{$gt: ["$x", 1]}, {$or: [{$eq: ["$x", "$y"]}, {$gt: ["$y", 1]}]}]}
+ }
+ }
+ ],
+ {
+ nReturned: 2,
+ expectedIndex: {x: 1, y: 1},
+ indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
+ });
+
+ // $cmp is not expected to use an index.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$cmp: ["$x", 1]}}}], {nReturned: 8});
+
+ // An constant expression is not expected to use an index.
+ confirmExpectedPipelineExecution([{$match: {$expr: 1}}], {nReturned: 9});
+
+ // Comparison of 2 fields is not expected to use an index.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$x", "$y"]}}}], {nReturned: 6});
+
+ // Comparison with field path length > 1 is not expected to use an index.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$a.b", 1]}}}], {nReturned: 1});
+
+ // $in with field path length > 1 is not expected to use an index.
+ confirmExpectedPipelineExecution([{$match: {$expr: {$in: ["$a.b", [1, 3]]}}}], {nReturned: 1});
+})();
diff --git a/src/mongo/db/matcher/expression_expr.cpp b/src/mongo/db/matcher/expression_expr.cpp
index 77349430e46..459a58307a5 100644
--- a/src/mongo/db/matcher/expression_expr.cpp
+++ b/src/mongo/db/matcher/expression_expr.cpp
@@ -31,13 +31,21 @@
#include "mongo/db/matcher/expression_expr.h"
namespace mongo {
+ExprMatchExpression::ExprMatchExpression(boost::intrusive_ptr<Expression> expr,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx)
+ : MatchExpression(MatchType::EXPRESSION), _expCtx(expCtx), _expression(expr) {}
+
ExprMatchExpression::ExprMatchExpression(BSONElement elem,
const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : MatchExpression(MatchType::EXPRESSION),
- _expCtx(expCtx),
- _expression(Expression::parseOperand(expCtx, elem, expCtx->variablesParseState)) {}
+ : ExprMatchExpression(Expression::parseOperand(expCtx, elem, expCtx->variablesParseState),
+ expCtx) {}
bool ExprMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const {
+ if (_rewriteResult && _rewriteResult->matchExpression() &&
+ !_rewriteResult->matchExpression()->matches(doc, details)) {
+ return false;
+ }
+
Document document(doc->toBSON());
auto value = _expression->evaluate(document);
return value.coerceToBool();
@@ -66,6 +74,10 @@ bool ExprMatchExpression::equivalent(const MatchExpression* other) const {
void ExprMatchExpression::_doSetCollator(const CollatorInterface* collator) {
_expCtx->setCollator(collator);
+
+ if (_rewriteResult && _rewriteResult->matchExpression()) {
+ _rewriteResult->matchExpression()->setCollator(collator);
+ }
}
@@ -82,7 +94,25 @@ std::unique_ptr<MatchExpression> ExprMatchExpression::shallowClone() const {
MatchExpression::ExpressionOptimizerFunc ExprMatchExpression::getOptimizer() const {
return [](std::unique_ptr<MatchExpression> expression) {
auto& exprMatchExpr = static_cast<ExprMatchExpression&>(*expression);
+
+ // If '_expression' can be rewritten to a MatchExpression, we will return a $and node with
+ // both the original ExprMatchExpression and the MatchExpression rewrite as children.
+ // Exiting early prevents additional calls to optimize from performing additional rewrites
+ // and adding duplicate MatchExpression sub-trees to the tree.
+ if (exprMatchExpr._rewriteResult) {
+ return expression;
+ }
+
exprMatchExpr._expression = exprMatchExpr._expression->optimize();
+ exprMatchExpr._rewriteResult =
+ RewriteExpr::rewrite(exprMatchExpr._expression, exprMatchExpr._expCtx->getCollator());
+
+ if (exprMatchExpr._rewriteResult->matchExpression()) {
+ auto andMatch = stdx::make_unique<AndMatchExpression>();
+ andMatch->add(exprMatchExpr._rewriteResult->releaseMatchExpression().release());
+ andMatch->add(expression.release());
+ expression = std::move(andMatch);
+ }
return expression;
};
diff --git a/src/mongo/db/matcher/expression_expr.h b/src/mongo/db/matcher/expression_expr.h
index 1a09e2a9334..bd50d2e94f3 100644
--- a/src/mongo/db/matcher/expression_expr.h
+++ b/src/mongo/db/matcher/expression_expr.h
@@ -32,6 +32,7 @@
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_tree.h"
+#include "mongo/db/matcher/rewrite_expr.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
@@ -46,8 +47,7 @@ public:
ExprMatchExpression(BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx);
ExprMatchExpression(boost::intrusive_ptr<Expression> expr,
- const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : MatchExpression(MatchType::EXPRESSION), _expCtx(expCtx), _expression(expr) {}
+ const boost::intrusive_ptr<ExpressionContext>& expCtx);
bool matchesSingleElement(const BSONElement& e, MatchDetails* details = nullptr) const final {
MONGO_UNREACHABLE;
@@ -96,7 +96,8 @@ private:
boost::intrusive_ptr<ExpressionContext> _expCtx;
boost::intrusive_ptr<Expression> _expression;
-};
+ boost::optional<RewriteExpr::RewriteResult> _rewriteResult;
+};
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_expr_test.cpp b/src/mongo/db/matcher/expression_expr_test.cpp
index e89ef9a5c39..4cb8be31e16 100644
--- a/src/mongo/db/matcher/expression_expr_test.cpp
+++ b/src/mongo/db/matcher/expression_expr_test.cpp
@@ -28,8 +28,10 @@
#include "mongo/platform/basic.h"
+#include "mongo/bson/json.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_expr.h"
+#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
@@ -41,57 +43,455 @@ namespace {
using unittest::assertGet;
-TEST(ExprMatchExpression, ComparisonToConstantMatchesCorrectly) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto match = BSON("a" << 5);
- auto notMatch = BSON("a" << 6);
+class ExprMatchTest : public mongo::unittest::Test {
+public:
+ ExprMatchTest() : _expCtx(new ExpressionContextForTest()) {}
+
+ void createMatcher(const BSONObj& matchExpr) {
+ _matchExpression = uassertStatusOK(
+ MatchExpressionParser::parse(matchExpr,
+ _expCtx,
+ ExtensionsCallbackNoop(),
+ MatchExpressionParser::kAllowAllSpecialFeatures));
+ _matchExpression = MatchExpression::optimize(std::move(_matchExpression));
+ }
+
+ void setCollator(CollatorInterface* collator) {
+ _expCtx->setCollator(collator);
+ if (_matchExpression) {
+ _matchExpression->setCollator(_expCtx->getCollator());
+ }
+ }
+
+ void setVariable(StringData name, Value val) {
+ auto varId = _expCtx->variablesParseState.defineVariable(name);
+ _expCtx->variables.setValue(varId, val);
+ }
+
+ bool matches(const BSONObj& doc) {
+ invariant(_matchExpression);
+ return _matchExpression->matchesBSON(doc);
+ }
+
+private:
+ const boost::intrusive_ptr<ExpressionContextForTest> _expCtx;
+ std::unique_ptr<MatchExpression> _matchExpression;
+};
+
+TEST_F(ExprMatchTest, ComparisonToConstantMatchesCorrectly) {
+ createMatcher(BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a" << 5))));
+
+ ASSERT_TRUE(matches(BSON("a" << 5)));
+
+ ASSERT_FALSE(matches(BSON("a" << 4)));
+ ASSERT_FALSE(matches(BSON("a" << 6)));
+}
- auto expression1 = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a" << 5)));
- Matcher matcher1(expression1,
- expCtx,
- ExtensionsCallbackNoop(),
- MatchExpressionParser::kAllowAllSpecialFeatures);
- ASSERT_TRUE(matcher1.matches(match));
- ASSERT_FALSE(matcher1.matches(notMatch));
+TEST_F(ExprMatchTest, ComparisonToConstantVariableMatchesCorrectly) {
+ setVariable("var", Value(5));
+ createMatcher(BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
+ << "$$var"))));
- auto varId = expCtx->variablesParseState.defineVariable("var");
- expCtx->variables.setValue(varId, Value(5));
- auto expression2 = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
- << "$$var")));
- Matcher matcher2(expression2,
- expCtx,
- ExtensionsCallbackNoop(),
- MatchExpressionParser::kAllowAllSpecialFeatures);
- ASSERT_TRUE(matcher2.matches(match));
- ASSERT_FALSE(matcher2.matches(notMatch));
-}
-
-TEST(ExprMatchExpression, ComparisonBetweenTwoFieldPathsMatchesCorrectly) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ASSERT_TRUE(matches(BSON("a" << 5)));
+
+ ASSERT_FALSE(matches(BSON("a" << 4)));
+ ASSERT_FALSE(matches(BSON("a" << 6)));
+}
- auto expression = BSON("$expr" << BSON("$gt" << BSON_ARRAY("$a"
- << "$b")));
- auto match = BSON("a" << 10 << "b" << 2);
- auto notMatch = BSON("a" << 2 << "b" << 10);
+TEST_F(ExprMatchTest, ComparisonBetweenTwoFieldPathsMatchesCorrectly) {
+ createMatcher(BSON("$expr" << BSON("$gt" << BSON_ARRAY("$a"
+ << "$b"))));
- Matcher matcher(expression,
- std::move(expCtx),
- ExtensionsCallbackNoop(),
- MatchExpressionParser::kAllowAllSpecialFeatures);
+ ASSERT_TRUE(matches(BSON("a" << 10 << "b" << 2)));
- ASSERT_TRUE(matcher.matches(match));
- ASSERT_FALSE(matcher.matches(notMatch));
+ ASSERT_FALSE(matches(BSON("a" << 2 << "b" << 2)));
+ ASSERT_FALSE(matches(BSON("a" << 2 << "b" << 10)));
}
-TEST(ExprMatchExpression, ComparisonThrowsWithUnboundVariable) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto expression = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
- << "$$var")));
- ASSERT_THROWS(ExprMatchExpression pipelineExpr(expression.firstElement(), std::move(expCtx)),
+TEST_F(ExprMatchTest, ComparisonThrowsWithUnboundVariable) {
+ ASSERT_THROWS(createMatcher(BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
+ << "$$var")))),
DBException);
}
-TEST(ExprMatchExpression, IdenticalPostOptimizedExpressionsAreEquivalent) {
+TEST_F(ExprMatchTest, EqWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$eq: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, EqWithRHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$eq: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, NeWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$ne: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+}
+
+TEST_F(ExprMatchTest, NeWithFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$ne: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+}
+
+TEST_F(ExprMatchTest, GtWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$gt: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+}
+
+TEST_F(ExprMatchTest, GtWithRHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$gt: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, GteWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$gte: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+}
+
+TEST_F(ExprMatchTest, GteWithRHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$gte: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, LtWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$lt: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, LtWithRHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$lt: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+}
+
+TEST_F(ExprMatchTest, LteWithLHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$lte: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+}
+
+TEST_F(ExprMatchTest, LteWithRHSFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$lte: [3, '$x']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("x" << 10)));
+
+ ASSERT_FALSE(matches(BSON("x" << 1)));
+}
+
+TEST_F(ExprMatchTest, AndMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$and: [{$eq: ['$x', 3]}, {$ne: ['$y', 4]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("x" << 3 << "y" << 5)));
+
+ ASSERT_FALSE(matches(BSON("x" << 10 << "y" << 5)));
+ ASSERT_FALSE(matches(BSON("x" << 3 << "y" << 4)));
+ ASSERT_FALSE(matches(BSON("x" << 10 << "y" << 5)));
+}
+
+TEST_F(ExprMatchTest, OrMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$or: [{$lte: ['$x', 3]}, {$gte: ['$y', 4]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("y" << 5)));
+
+ ASSERT_FALSE(matches(BSON("x" << 10)));
+ ASSERT_FALSE(matches(BSON("y" << 1)));
+}
+
+TEST_F(ExprMatchTest, AndNestedWithinOrMatchesCorrectly) {
+ createMatcher(fromjson(
+ "{$expr: {$or: [{$and: [{$eq: ['$x', 3]}, {$gt: ['$z', 5]}]}, {$lt: ['$y', 4]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3 << "z" << 7)));
+ ASSERT_TRUE(matches(BSON("y" << 1)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3 << "z" << 3)));
+ ASSERT_FALSE(matches(BSON("y" << 5)));
+}
+
+TEST_F(ExprMatchTest, OrNestedWithinAndMatchesCorrectly) {
+ createMatcher(fromjson(
+ "{$expr: {$and: [{$or: [{$eq: ['$x', 3]}, {$eq: ['$z', 5]}]}, {$eq: ['$y', 4]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3 << "y" << 4)));
+ ASSERT_TRUE(matches(BSON("z" << 5 << "y" << 4)));
+ ASSERT_TRUE(matches(BSON("x" << 3 << "z" << 5 << "y" << 4)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3 << "z" << 5)));
+ ASSERT_FALSE(matches(BSON("y" << 4)));
+ ASSERT_FALSE(matches(BSON("x" << 3 << "y" << 10)));
+}
+
+TEST_F(ExprMatchTest, InWithLhsFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$in: ['$x', [1, 2, 3]]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+
+ ASSERT_FALSE(matches(BSON("x" << 5)));
+ ASSERT_FALSE(matches(BSON("y" << 2)));
+ ASSERT_FALSE(matches(BSON("x" << BSON("y" << 2))));
+}
+
+TEST_F(ExprMatchTest, InWithLhsFieldPathAndArrayAsConstMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$in: ['$x', {$const: [1, 2, 3]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+
+ ASSERT_FALSE(matches(BSON("x" << 5)));
+ ASSERT_FALSE(matches(BSON("y" << 2)));
+ ASSERT_FALSE(matches(BSON("x" << BSON("y" << 2))));
+}
+
+TEST_F(ExprMatchTest, CmpMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$cmp: ['$x', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+ ASSERT_TRUE(matches(BSON("x" << 4)));
+ ASSERT_TRUE(matches(BSON("y" << 3)));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+}
+
+TEST_F(ExprMatchTest, ConstantLiteralExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$literal: {$eq: ['$x', 10]}}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, ConstantPositiveNumberExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: 1}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, ConstantNegativeNumberExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: -1}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, ConstantNumberZeroExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: 0}"));
+
+ ASSERT_FALSE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, ConstantTrueValueExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: true}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, ConstantFalseValueExpressionMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: false}"));
+
+ ASSERT_FALSE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, EqWithTwoFieldPathsMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$eq: ['$x', '$y']}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2 << "y" << 2)));
+
+ ASSERT_FALSE(matches(BSON("x" << 2 << "y" << 3)));
+ ASSERT_FALSE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, EqWithTwoConstantsMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$eq: [3, 4]}}"));
+
+ ASSERT_FALSE(matches(BSON("x" << 3)));
+}
+
+TEST_F(ExprMatchTest, EqWithDottedFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$eq: ['$x.y', 3]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << BSON("y" << 3))));
+
+ ASSERT_FALSE(matches(BSON("x" << BSON("y" << BSON_ARRAY(3)))));
+ ASSERT_FALSE(matches(BSON("x" << BSON_ARRAY(BSON("y" << 3)))));
+ ASSERT_FALSE(matches(BSON("x" << BSON_ARRAY(BSON("y" << BSON_ARRAY(3))))));
+}
+
+TEST_F(ExprMatchTest, InWithDottedFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$in: ['$x.y', [1, 2, 3]]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << BSON("y" << 3))));
+
+ ASSERT_FALSE(matches(BSON("x" << BSON("y" << BSON_ARRAY(3)))));
+}
+
+TEST_F(ExprMatchTest, AndWithNoMatchRewritableChildrenMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$and: [{$eq: ['$w', '$x']}, {$eq: ['$y', '$z']}]}}"));
+
+ ASSERT_TRUE(matches(BSON("w" << 2 << "x" << 2 << "y" << 5 << "z" << 5)));
+
+ ASSERT_FALSE(matches(BSON("w" << 1 << "x" << 2 << "y" << 5 << "z" << 5)));
+ ASSERT_FALSE(matches(BSON("w" << 2 << "x" << 2 << "y" << 5 << "z" << 6)));
+ ASSERT_FALSE(matches(BSON("w" << 2 << "y" << 5)));
+}
+
+TEST_F(ExprMatchTest, OrWithDistinctMatchRewritableAndNonMatchRewritableChildrenMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$or: [{$eq: ['$x', 1]}, {$eq: ['$y', '$z']}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1)));
+ ASSERT_TRUE(matches(BSON("y" << 1 << "z" << 1)));
+
+ ASSERT_FALSE(matches(BSON("x" << 2 << "y" << 3)));
+ ASSERT_FALSE(matches(BSON("y" << 1)));
+ ASSERT_FALSE(matches(BSON("y" << 1 << "z" << 2)));
+}
+
+TEST_F(ExprMatchTest, InWithoutLhsFieldPathMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$in: [2, [1, 2, 3]]}}"));
+ ASSERT_TRUE(matches(BSON("x" << 2)));
+
+ createMatcher(fromjson("{$expr: {$in: [2, [5, 6, 7]]}}"));
+ ASSERT_FALSE(matches(BSON("x" << 2)));
+}
+
+TEST_F(ExprMatchTest, NestedAndWithTwoFieldPathsWithinOrMatchesCorrectly) {
+ createMatcher(fromjson(
+ "{$expr: {$or: [{$and: [{$eq: ['$x', '$w']}, {$eq: ['$z', 5]}]}, {$eq: ['$y', 4]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 2 << "w" << 2 << "z" << 5)));
+ ASSERT_TRUE(matches(BSON("y" << 4)));
+
+ ASSERT_FALSE(matches(BSON("x" << 2 << "w" << 4)));
+ ASSERT_FALSE(matches(BSON("y" << 5)));
+}
+
+TEST_F(ExprMatchTest, AndWithDistinctMatchAndNonMatchSubTreeMatchesCorrectly) {
+ createMatcher(fromjson("{$expr: {$and: [{$eq: ['$x', 1]}, {$eq: ['$y', '$z']}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 1 << "y" << 2 << "z" << 2)));
+
+ ASSERT_FALSE(matches(BSON("x" << 2 << "y" << 2 << "z" << 2)));
+ ASSERT_FALSE(matches(BSON("x" << 1 << "y" << 2 << "z" << 10)));
+ ASSERT_FALSE(matches(BSON("x" << 1 << "y" << 2)));
+}
+
+TEST_F(ExprMatchTest, ComplexExprMatchesCorrectly) {
+ createMatcher(
+ fromjson("{"
+ " $expr: {"
+ " $and: ["
+ " {$eq: ['$a', 1]},"
+ " {$eq: ['$b', '$c']},"
+ " {"
+ " $or: ["
+ " {$eq: ['$d', 1]},"
+ " {$eq: ['$e', 3]},"
+ " {"
+ " $and: ["
+ " {$eq: ['$f', 1]},"
+ " {$eq: ['$g', '$h']},"
+ " {$or: [{$eq: ['$i', 3]}, {$eq: ['$j', '$k']}]}"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ "}"));
+
+ ASSERT_TRUE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "d" << 1)));
+ ASSERT_TRUE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "e" << 3)));
+ ASSERT_TRUE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "f" << 1 << "i" << 3)));
+ ASSERT_TRUE(
+ matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "f" << 1 << "j" << 5 << "k" << 5)));
+
+ ASSERT_FALSE(matches(BSON("a" << 1)));
+ ASSERT_FALSE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3)));
+ ASSERT_FALSE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "d" << 5)));
+ ASSERT_FALSE(matches(BSON("a" << 1 << "b" << 3 << "c" << 3 << "j" << 5 << "k" << 10)));
+}
+
+TEST_F(ExprMatchTest,
+ OrWithAndContainingMatchRewritableAndNonMatchRewritableChildMatchesCorrectly) {
+ createMatcher(fromjson(
+ "{$expr: {$or: [{$eq: ['$x', 3]}, {$and: [{$eq: ['$y', 4]}, {$eq: ['$y', '$z']}]}]}}"));
+
+ ASSERT_TRUE(matches(BSON("x" << 3)));
+ ASSERT_TRUE(matches(BSON("y" << 4 << "z" << 4)));
+
+ ASSERT_FALSE(matches(BSON("x" << 4)));
+ ASSERT_FALSE(matches(BSON("y" << 4 << "z" << 5)));
+}
+
+TEST_F(ExprMatchTest, InitialCollationUsedForComparisons) {
+ auto collator =
+ stdx::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kToLowerString);
+ setCollator(collator.get());
+ createMatcher(fromjson("{$expr: {$eq: ['$x', 'abc']}}"));
+
+ ASSERT_TRUE(matches(BSON("x"
+ << "AbC")));
+
+ ASSERT_FALSE(matches(BSON("x"
+ << "cba")));
+}
+
+TEST_F(ExprMatchTest, SetCollatorChangesCollationUsedForComparisons) {
+ createMatcher(fromjson("{$expr: {$eq: ['$x', 'abc']}}"));
+
+ auto collator =
+ stdx::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kToLowerString);
+ setCollator(collator.get());
+
+ ASSERT_TRUE(matches(BSON("x"
+ << "AbC")));
+
+ ASSERT_FALSE(matches(BSON("x"
+ << "cba")));
+}
+
+TEST(ExprMatchTest, IdenticalPostOptimizedExpressionsAreEquivalent) {
BSONObj expression = BSON("$expr" << BSON("$multiply" << BSON_ARRAY(2 << 2)));
BSONObj expressionEquiv = BSON("$expr" << BSON("$const" << 4));
BSONObj expressionNotEquiv = BSON("$expr" << BSON("$const" << 10));
@@ -116,7 +516,7 @@ TEST(ExprMatchExpression, IdenticalPostOptimizedExpressionsAreEquivalent) {
ASSERT_FALSE(pipelineExpr->equivalent(&pipelineExprNotEquiv));
}
-TEST(ExprMatchExpression, ExpressionOptimizeRewritesVariableDereferenceAsConstant) {
+TEST(ExprMatchTest, ExpressionOptimizeRewritesVariableDereferenceAsConstant) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto varId = expCtx->variablesParseState.defineVariable("var");
expCtx->variables.setValue(varId, Value(4));
@@ -142,7 +542,7 @@ TEST(ExprMatchExpression, ExpressionOptimizeRewritesVariableDereferenceAsConstan
ASSERT_FALSE(pipelineExpr.equivalent(&pipelineExprNotEquiv));
}
-TEST(ExprMatchExpression, ShallowClonedExpressionIsEquivalentToOriginal) {
+TEST(ExprMatchTest, ShallowClonedExpressionIsEquivalentToOriginal) {
BSONObj expression = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a" << 5)));
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
@@ -151,25 +551,5 @@ TEST(ExprMatchExpression, ShallowClonedExpressionIsEquivalentToOriginal) {
ASSERT_TRUE(pipelineExpr.equivalent(shallowClone.get()));
}
-TEST(ExprMatchExpression, SetCollatorChangesCollationUsedForComparisons) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- auto match = BSON("a"
- << "abc");
- auto notMatch = BSON("a"
- << "ABC");
-
- auto expression = BSON("$expr" << BSON("$eq" << BSON_ARRAY("$a"
- << "abc")));
- auto matchExpression = assertGet(MatchExpressionParser::parse(expression, expCtx));
- ASSERT_TRUE(matchExpression->matchesBSON(match));
- ASSERT_FALSE(matchExpression->matchesBSON(notMatch));
-
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual);
- matchExpression->setCollator(&collator);
-
- ASSERT_TRUE(matchExpression->matchesBSON(match));
- ASSERT_TRUE(matchExpression->matchesBSON(notMatch));
-}
-
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp
index ca6e1ec0567..fef34675935 100644
--- a/src/mongo/db/matcher/expression_serialization_test.cpp
+++ b/src/mongo/db/matcher/expression_serialization_test.cpp
@@ -974,8 +974,7 @@ TEST(SerializeBasic, ExpressionNotWithGeoSerializesCorrectly) {
ASSERT_BSONOBJ_EQ(
*reserialized.getQuery(),
fromjson("{$nor: [{$and: [{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: "
- "[[[0,0], "
- "[5,0], [5, 5], [0, 5], [0, 0]]]}}}}]}]}"));
+ "[[[ 0, 0 ], [5, 0], [5, 5], [0, 5], [0, 0]]]}}}}]}]}"));
ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
BSONObj obj =
diff --git a/src/mongo/db/matcher/rewrite_expr.cpp b/src/mongo/db/matcher/rewrite_expr.cpp
index d5d0e4605ac..304a850f592 100644
--- a/src/mongo/db/matcher/rewrite_expr.cpp
+++ b/src/mongo/db/matcher/rewrite_expr.cpp
@@ -40,20 +40,20 @@ namespace mongo {
using CmpOp = ExpressionCompare::CmpOp;
-RewriteExpr::RewriteResult RewriteExpr::rewrite(
- const boost::intrusive_ptr<Expression>& expression) {
+RewriteExpr::RewriteResult RewriteExpr::rewrite(const boost::intrusive_ptr<Expression>& expression,
+ const CollatorInterface* collator) {
LOG(5) << "Expression prior to rewrite: " << expression->serialize(false);
- RewriteExpr rewriteExpr;
+ RewriteExpr rewriteExpr(collator);
std::unique_ptr<MatchExpression> matchExpression;
if (auto matchTree = rewriteExpr._rewriteExpression(expression)) {
matchExpression = std::move(matchTree);
LOG(5) << "Post-rewrite MatchExpression: " << matchExpression->toString();
+ matchExpression = MatchExpression::optimize(std::move(matchExpression));
+ LOG(5) << "Post-rewrite/post-optimized MatchExpression: " << matchExpression->toString();
}
- // TODO SERVER-30989: Optimize via MatchExpression::optimize().
-
return {std::move(matchExpression),
std::move(rewriteExpr._matchExprStringStorage),
std::move(rewriteExpr._matchExprElemStorage)};
@@ -151,6 +151,7 @@ std::unique_ptr<MatchExpression> RewriteExpr::_rewriteInExpression(
inValues.elems(elementList);
auto matchInExpr = stdx::make_unique<InMatchExpression>();
+ matchInExpr->setCollator(_collator);
uassertStatusOK(matchInExpr->init(fieldPath));
uassertStatusOK(matchInExpr->setEqualities(elementList));
@@ -254,6 +255,7 @@ std::unique_ptr<MatchExpression> RewriteExpr::_buildComparisonMatchExpression(
}
uassertStatusOK(compMatchExpr->init(fieldAndValue.fieldName(), fieldAndValue));
+ compMatchExpr->setCollator(_collator);
if (notMatchExpr) {
uassertStatusOK(notMatchExpr->init(compMatchExpr.release()));
diff --git a/src/mongo/db/matcher/rewrite_expr.h b/src/mongo/db/matcher/rewrite_expr.h
index ec5be2c2641..5bbb8e088aa 100644
--- a/src/mongo/db/matcher/rewrite_expr.h
+++ b/src/mongo/db/matcher/rewrite_expr.h
@@ -52,10 +52,14 @@ public:
_matchExprStringStorage(std::move(matchExprStringStorage)),
_matchExprElemStorage(std::move(matchExprElemStorage)) {}
- const MatchExpression* matchExpression() const {
+ MatchExpression* matchExpression() const {
return _matchExpression.get();
}
+ std::unique_ptr<MatchExpression> releaseMatchExpression() {
+ return std::move(_matchExpression);
+ }
+
private:
std::unique_ptr<MatchExpression> _matchExpression;
@@ -72,9 +76,12 @@ public:
* superset of the documents matched by 'expr'. Returns the MatchExpression as a RewriteResult.
* If a rewrite is not possible, RewriteResult::matchExpression() will return a nullptr.
*/
- static RewriteResult rewrite(const boost::intrusive_ptr<Expression>& expr);
+ static RewriteResult rewrite(const boost::intrusive_ptr<Expression>& expr,
+ const CollatorInterface* collator);
private:
+ RewriteExpr(const CollatorInterface* collator) : _collator(collator) {}
+
// Returns rewritten MatchExpression or null unique_ptr if not rewritable.
std::unique_ptr<MatchExpression> _rewriteExpression(
const boost::intrusive_ptr<Expression>& currExprNode);
@@ -109,6 +116,7 @@ private:
std::vector<BSONObj> _matchExprElemStorage;
std::vector<std::string> _matchExprStringStorage;
+ const CollatorInterface* _collator;
};
} // namespace mongo
diff --git a/src/mongo/db/matcher/rewrite_expr_test.cpp b/src/mongo/db/matcher/rewrite_expr_test.cpp
index bb86ae761f7..ae9f30d6d5d 100644
--- a/src/mongo/db/matcher/rewrite_expr_test.cpp
+++ b/src/mongo/db/matcher/rewrite_expr_test.cpp
@@ -50,7 +50,7 @@ void testExprRewrite(BSONObj expr, BSONObj expectedMatch) {
auto expression =
Expression::parseOperand(expCtx, expr.firstElement(), expCtx->variablesParseState);
- auto result = RewriteExpr::rewrite(expression);
+ auto result = RewriteExpr::rewrite(expression, expCtx->getCollator());
// Confirm expected match.
if (!expectedMatch.isEmpty()) {
@@ -246,14 +246,14 @@ TEST(RewriteExpr, InWithoutLhsFieldPathDoesNotRewriteToMatch) {
TEST(RewriteExpr, NestedAndWithTwoFieldPathsWithinOrPartiallyRewriteToMatch) {
const BSONObj expr = fromjson(
"{$expr: {$or: [{$and: [{$eq: ['$x', '$w']}, {$eq: ['$z', 5]}]}, {$eq: ['$y', 4]}]}}");
- const BSONObj expectedMatch = fromjson("{$or: [{$and: [{z: {$eq: 5}}]}, {y: {$eq: 4}}]}");
+ const BSONObj expectedMatch = fromjson("{$or: [{z: {$eq: 5}}, {y: {$eq: 4}}]}");
testExprRewrite(expr, expectedMatch);
}
TEST(RewriteExpr, AndWithDistinctMatchAndNonMatchSubTreeSplitsOnRewrite) {
const BSONObj expr = fromjson("{$expr: {$and: [{$eq: ['$x', 1]}, {$eq: ['$y', '$z']}]}}");
- const BSONObj expectedMatch = fromjson("{$and: [{x: {$eq: 1}}]}");
+ const BSONObj expectedMatch = fromjson("{x: {$eq: 1}}");
testExprRewrite(expr, expectedMatch);
}
@@ -291,7 +291,7 @@ TEST(RewriteExpr, ComplexSupersetMatchRewritesToMatchSuperset) {
" $or: ["
" {d: {$eq: 1}},"
" {e: {$eq: 3}},"
- " {$and: [{f: {$eq: 1}}]}"
+ " {f: {$eq: 1}}"
" ]"
" }"
" ]"
@@ -303,7 +303,7 @@ TEST(RewriteExpr, ComplexSupersetMatchRewritesToMatchSuperset) {
TEST(RewriteExpr, OrWithAndContainingMatchAndNonMatchChildPartiallyRewritesToMatch) {
const BSONObj expr = fromjson(
"{$expr: {$or: [{$eq: ['$x', 3]}, {$and: [{$eq: ['$y', 4]}, {$eq: ['$y', '$z']}]}]}}");
- const BSONObj expectedMatch = fromjson("{$or: [{x: {$eq: 3}}, {$and: [{y: {$eq: 4}}]}]}");
+ const BSONObj expectedMatch = fromjson("{$or: [{x: {$eq: 3}}, {y: {$eq: 4}}]}");
testExprRewrite(expr, expectedMatch);
}
diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp
index 434bfabc00f..9e6677f8dcb 100644
--- a/src/mongo/db/pipeline/document_source_lookup_test.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp
@@ -798,7 +798,8 @@ TEST_F(DocumentSourceLookUpTest, ExprEmbeddedInMatchExpressionShouldBeOptimized)
BSONObjBuilder builder;
matchSource.getMatchExpression()->serialize(&builder);
auto serializedMatch = builder.obj();
- auto expectedMatch = fromjson("{$expr: {$eq: ['$_id', {$const: 5}]}}");
+ auto expectedMatch =
+ fromjson("{$and: [{_id: {$eq: 5}}, {$expr: {$eq: ['$_id', {$const: 5}]}}]}");
ASSERT_VALUE_EQ(Value(serializedMatch), Value(expectedMatch));
}