summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorHenri Nikku <henri.nikku@mongodb.com>2022-07-25 17:00:24 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-07-25 17:55:30 +0000
commit3b51f4acaa6a57e8ced17971f85c7d6d35ac0cca (patch)
tree46df2d98d93807cd2d91787611e30996ae5a96dd /src/mongo/db/matcher
parent0e465a0d7adefc42b9399fcbde011d712785ad76 (diff)
downloadmongo-3b51f4acaa6a57e8ced17971f85c7d6d35ac0cca.tar.gz
SERVER-65049 Optimize away { $expr : true } in $match clauses
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/expression_expr.cpp11
-rw-r--r--src/mongo/db/matcher/expression_expr.h2
-rw-r--r--src/mongo/db/matcher/expression_expr_test.cpp73
3 files changed, 77 insertions, 9 deletions
diff --git a/src/mongo/db/matcher/expression_expr.cpp b/src/mongo/db/matcher/expression_expr.cpp
index 567039995f6..a1d151b8e1c 100644
--- a/src/mongo/db/matcher/expression_expr.cpp
+++ b/src/mongo/db/matcher/expression_expr.cpp
@@ -126,6 +126,11 @@ std::unique_ptr<MatchExpression> ExprMatchExpression::shallowClone() const {
return clone;
}
+bool ExprMatchExpression::isTriviallyTrue() const {
+ auto exprConst = dynamic_cast<ExpressionConstant*>(_expression.get());
+ return exprConst && exprConst->getValue().coerceToBool();
+}
+
MatchExpression::ExpressionOptimizerFunc ExprMatchExpression::getOptimizer() const {
return [](std::unique_ptr<MatchExpression> expression) {
auto& exprMatchExpr = static_cast<ExprMatchExpression&>(*expression);
@@ -150,6 +155,12 @@ MatchExpression::ExpressionOptimizerFunc ExprMatchExpression::getOptimizer() con
expression = MatchExpression::optimize(std::move(andMatch));
}
+ // Replace trivially true expression with an empty AND since the planner doesn't always
+ // check for 'isTriviallyTrue()'.
+ if (expression->isTriviallyTrue()) {
+ expression = std::make_unique<AndMatchExpression>();
+ }
+
return expression;
};
}
diff --git a/src/mongo/db/matcher/expression_expr.h b/src/mongo/db/matcher/expression_expr.h
index 0d1110f5bac..5e5be65cbbe 100644
--- a/src/mongo/db/matcher/expression_expr.h
+++ b/src/mongo/db/matcher/expression_expr.h
@@ -77,6 +77,8 @@ public:
void serialize(BSONObjBuilder* out, bool includePath) const final;
+ bool isTriviallyTrue() const final;
+
bool equivalent(const MatchExpression* other) const final;
MatchCategory getCategory() const final {
diff --git a/src/mongo/db/matcher/expression_expr_test.cpp b/src/mongo/db/matcher/expression_expr_test.cpp
index 801b3634813..01bd01818b0 100644
--- a/src/mongo/db/matcher/expression_expr_test.cpp
+++ b/src/mongo/db/matcher/expression_expr_test.cpp
@@ -78,6 +78,10 @@ public:
return _matchExpression->matchesBSON(doc);
}
+ MatchExpression* getMatchExpression() {
+ return _matchExpression.get();
+ }
+
ExprMatchExpression* getExprMatchExpression() {
return checked_cast<ExprMatchExpression*>(_matchExpression.get());
}
@@ -606,9 +610,13 @@ TEST_F(ExprMatchTest, ReturnsFalseInsteadOfErrorWithFailpointSet) {
}
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));
+ BSONObj expression =
+ BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD"
+ << BSON("$multiply" << BSON_ARRAY(2 << 2)))));
+ BSONObj expressionEquiv =
+ BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD" << BSON("$const" << 4))));
+ BSONObj expressionNotEquiv =
+ BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD" << BSON("$const" << 10))));
// Create and optimize an ExprMatchExpression.
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
@@ -634,11 +642,12 @@ TEST(ExprMatchTest, ExpressionOptimizeRewritesVariableDereferenceAsConstant) {
const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto varId = expCtx->variablesParseState.defineVariable("var");
expCtx->variables.setConstantValue(varId, Value(4));
-
- BSONObj expression = BSON("$expr"
- << "$$var");
- BSONObj expressionEquiv = BSON("$expr" << BSON("$const" << 4));
- BSONObj expressionNotEquiv = BSON("$expr" << BSON("$const" << 10));
+ BSONObj expression = BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD"
+ << "$$var")));
+ BSONObj expressionEquiv =
+ BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD" << BSON("$const" << 4))));
+ BSONObj expressionNotEquiv =
+ BSON("$expr" << BSON("$ifNull" << BSON_ARRAY("$NO_SUCH_FIELD" << BSON("$const" << 10))));
// Create and optimize an ExprMatchExpression.
std::unique_ptr<MatchExpression> matchExpr =
@@ -728,8 +737,54 @@ TEST(ExprMatchTest, OptimizingExprAbsorbsAndOfAnd) {
ASSERT_BSONOBJ_EQ(serialized, expectedSerialization);
}
+TEST(ExprMatchTest, OptimizingExprRemovesTrueConstantExpression) {
+ auto exprBson = fromjson("{$expr: true}");
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+
+ auto matchExpr =
+ std::make_unique<ExprMatchExpression>(exprBson.firstElement(), std::move(expCtx));
+ auto optimized = MatchExpression::optimize(std::move(matchExpr));
+
+ auto serialization = optimized->serialize();
+ auto expectedSerialization = fromjson("{}");
+ ASSERT_BSONOBJ_EQ(serialization, expectedSerialization);
+}
+
+TEST(ExprMatchTest, OptimizingExprRemovesTruthyConstantExpression) {
+ auto exprBson = fromjson("{$expr: {$concat: ['a', 'b', 'c']}}");
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+
+ auto matchExpr =
+ std::make_unique<ExprMatchExpression>(exprBson.firstElement(), std::move(expCtx));
+ auto optimized = MatchExpression::optimize(std::move(matchExpr));
+
+ auto serialization = optimized->serialize();
+ auto expectedSerialization = fromjson("{}");
+ ASSERT_BSONOBJ_EQ(serialization, expectedSerialization);
+}
+
+TEST_F(ExprMatchTest, ExprWithTrueConstantExpressionIsTriviallyTrue) {
+ createMatcher(fromjson("{$expr: true}"));
+ ASSERT_TRUE(getMatchExpression()->isTriviallyTrue());
+}
+
+TEST_F(ExprMatchTest, ExprWithTruthyConstantExpressionIsTriviallyTrue) {
+ createMatcher(fromjson("{$expr: {$concat: ['a', 'b', 'c']}}"));
+ ASSERT_TRUE(getMatchExpression()->isTriviallyTrue());
+}
+
+TEST_F(ExprMatchTest, ExprWithNonConstantExpressionIsNotTriviallyTrue) {
+ createMatcher(fromjson("{$expr: {$concat: ['$a', '$b', '$c']}}"));
+ ASSERT_FALSE(getMatchExpression()->isTriviallyTrue());
+}
+
+TEST_F(ExprMatchTest, ExprWithFalsyConstantExpressionIsNotTriviallyTrue) {
+ createMatcher(fromjson("{$expr: {$sum: [1, -1]}}"));
+ ASSERT_FALSE(getMatchExpression()->isTriviallyTrue());
+}
+
TEST_F(ExprMatchTest, ExpressionEvaluationReturnsResultsCorrectly) {
- createMatcher(fromjson("{$expr: -2}"));
+ createMatcher(fromjson("{$expr: {$ifNull: ['$NO_SUCH_FIELD', -2]}}"));
BSONMatchableDocument document{BSONObj{}};
auto expressionResult = getExprMatchExpression()->evaluateExpression(&document);
ASSERT_TRUE(expressionResult.integral());