diff options
author | Henri Nikku <henri.nikku@mongodb.com> | 2022-07-25 17:00:24 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-07-25 17:55:30 +0000 |
commit | 3b51f4acaa6a57e8ced17971f85c7d6d35ac0cca (patch) | |
tree | 46df2d98d93807cd2d91787611e30996ae5a96dd /src/mongo/db/matcher | |
parent | 0e465a0d7adefc42b9399fcbde011d712785ad76 (diff) | |
download | mongo-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.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_expr.h | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_expr_test.cpp | 73 |
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()); |