summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp76
-rw-r--r--src/mongo/db/matcher/expression_algo.h34
-rw-r--r--src/mongo/db/matcher/expression_algo_test.cpp42
-rw-r--r--src/mongo/db/matcher/expression_internal_bucket_geo_within.h5
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp3
-rw-r--r--src/mongo/db/matcher/expression_leaf.h11
-rw-r--r--src/mongo/db/matcher/expression_optimize_test.cpp21
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp6
-rw-r--r--src/mongo/db/matcher/expression_tree.cpp27
9 files changed, 185 insertions, 40 deletions
diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp
index 5d56608227d..558ef533380 100644
--- a/src/mongo/db/matcher/expression_algo.cpp
+++ b/src/mongo/db/matcher/expression_algo.cpp
@@ -31,6 +31,7 @@
#include "mongo/platform/basic.h"
#include "mongo/base/checked_cast.h"
+#include "mongo/bson/unordered_fields_bsonobj_comparator.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_algo.h"
#include "mongo/db/matcher/expression_array.h"
@@ -374,7 +375,7 @@ unique_ptr<MatchExpression> createNorOfNodes(std::vector<unique_ptr<MatchExpress
*/
std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchExpressionByFunction(
unique_ptr<MatchExpression> expr,
- const std::set<std::string>& fields,
+ const OrderedPathSet& fields,
expression::ShouldSplitExprFunc shouldSplitOut) {
if (shouldSplitOut(*expr, fields)) {
// 'expr' satisfies our split condition and can be completely split out.
@@ -440,7 +441,7 @@ std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchEx
bool pathDependenciesAreExact(StringData key, const MatchExpression* expr) {
DepsTracker columnDeps;
expr->addDependencies(&columnDeps);
- return !columnDeps.needWholeDocument && columnDeps.fields == std::set{key.toString()};
+ return !columnDeps.needWholeDocument && columnDeps.fields == OrderedPathSet{key.toString()};
}
bool tryAddExprHelper(StringData path,
@@ -769,7 +770,6 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) {
return false;
}
-// Checks if 'expr' has any children which do not have renaming implemented.
bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr) {
if (expr.matchType() == MatchExpression::MatchType::EXPRESSION) {
return true;
@@ -786,7 +786,33 @@ bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr) {
return true;
}
-bool isIndependentOf(const MatchExpression& expr, const std::set<std::string>& pathSet) {
+bool containsDependency(const OrderedPathSet& testSet, const OrderedPathSet& prefixCandidates) {
+ if (testSet.empty()) {
+ return false;
+ }
+
+ PathComparator pathComparator;
+ auto i2 = testSet.begin();
+ for (auto p1 : prefixCandidates) {
+ while (pathComparator(*i2, p1)) {
+ ++i2;
+ if (i2 == testSet.end()) {
+ return false;
+ }
+ }
+ // At this point we know that p1 <= *i2, so it may be identical or a path prefix.
+ if (p1 == *i2 || isPathPrefixOf(p1, *i2)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool areIndependent(const OrderedPathSet& pathSet1, const OrderedPathSet& pathSet2) {
+ return !containsDependency(pathSet1, pathSet2) && !containsDependency(pathSet2, pathSet1);
+}
+
+bool isIndependentOf(const MatchExpression& expr, const OrderedPathSet& pathSet) {
// Any expression types that do not have renaming implemented cannot have their independence
// evaluated here. See applyRenamesToExpression().
if (!hasOnlyRenameableMatchExpressionChildren(expr)) {
@@ -795,35 +821,43 @@ bool isIndependentOf(const MatchExpression& expr, const std::set<std::string>& p
auto depsTracker = DepsTracker{};
expr.addDependencies(&depsTracker);
- return std::none_of(
- depsTracker.fields.begin(), depsTracker.fields.end(), [&pathSet](auto&& field) {
- return pathSet.find(field) != pathSet.end() ||
- std::any_of(pathSet.begin(), pathSet.end(), [&field](auto&& path) {
- return expression::isPathPrefixOf(field, path) ||
- expression::isPathPrefixOf(path, field);
- });
- });
+ // Match expressions that generate random numbers can't be safely split out and pushed down.
+ if (depsTracker.needRandomGenerator || depsTracker.needWholeDocument) {
+ return false;
+ }
+ return areIndependent(pathSet, depsTracker.fields);
}
-bool isOnlyDependentOn(const MatchExpression& expr, const std::set<std::string>& pathSet) {
+bool isOnlyDependentOn(const MatchExpression& expr, const OrderedPathSet& pathSet) {
// Any expression types that do not have renaming implemented cannot have their independence
// evaluated here. See applyRenamesToExpression().
if (!hasOnlyRenameableMatchExpressionChildren(expr)) {
return false;
}
- auto depsTracker = DepsTracker{};
- expr.addDependencies(&depsTracker);
- return std::all_of(depsTracker.fields.begin(), depsTracker.fields.end(), [&](auto&& field) {
- return std::any_of(pathSet.begin(), pathSet.end(), [&](auto&& path) {
- return path == field || isPathPrefixOf(path, field);
- });
- });
+ // The approach below takes only O(n log n) time.
+
+ // Find the unique dependencies of pathSet.
+ auto pathsDeps =
+ DepsTracker::simplifyDependencies(pathSet, DepsTracker::TruncateToRootLevel::no);
+ auto pathsDepsCopy = OrderedPathSet(pathsDeps.begin(), pathsDeps.end());
+
+ // Now add the match expression's paths and see if the dependencies are the same.
+ auto exprDepsTracker = DepsTracker{};
+ expr.addDependencies(&exprDepsTracker);
+ // Match expressions that generate random numbers can't be safely split out and pushed down.
+ if (exprDepsTracker.needRandomGenerator) {
+ return false;
+ }
+ pathsDepsCopy.insert(exprDepsTracker.fields.begin(), exprDepsTracker.fields.end());
+
+ return pathsDeps ==
+ DepsTracker::simplifyDependencies(pathsDepsCopy, DepsTracker::TruncateToRootLevel::no);
}
std::pair<unique_ptr<MatchExpression>, unique_ptr<MatchExpression>> splitMatchExpressionBy(
unique_ptr<MatchExpression> expr,
- const std::set<std::string>& fields,
+ const OrderedPathSet& fields,
const StringMap<std::string>& renames,
ShouldSplitExprFunc func /*= isIndependentOf */) {
auto splitExpr = splitMatchExpressionByFunction(std::move(expr), fields, func);
diff --git a/src/mongo/db/matcher/expression_algo.h b/src/mongo/db/matcher/expression_algo.h
index 0fdbac756a0..fc42fca7814 100644
--- a/src/mongo/db/matcher/expression_algo.h
+++ b/src/mongo/db/matcher/expression_algo.h
@@ -34,6 +34,7 @@
#include <set>
#include "mongo/base/string_data.h"
+#include "mongo/db/pipeline/dependencies.h"
#include "mongo/util/string_map.h"
namespace mongo {
@@ -53,6 +54,11 @@ using NodeTraversalFunc = std::function<void(MatchExpression*, std::string)>;
bool hasExistencePredicateOnPath(const MatchExpression& expr, StringData path);
/**
+ * Checks if 'expr' has any children which do not have renaming implemented.
+ */
+bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr);
+
+/**
* Returns true if the documents matched by 'lhs' are a subset of the documents matched by
* 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise.
*
@@ -85,17 +91,32 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs);
* For example, {a: "foo", b: "bar"} is splittable by "b", while
* {$or: [{a: {$eq: "foo"}}, {b: {$eq: "bar"}}]} is not splittable by "b", due to the $or.
*/
-bool isSplittableBy(const MatchExpression& expr, const std::set<std::string>& pathSet);
+bool isSplittableBy(const MatchExpression& expr, const OrderedPathSet& pathSet);
+
+/**
+ * True if no path in either set is contained by a path in the other. Does not check for
+ * dependencies within each of the sets, just across sets. Runs in 0(n) time.
+ *
+ * areIndependent([a.b, b, a], [c]) --> true
+ * areIndependent([a.b, b, a], [a.b.f]) --> false
+ */
+bool areIndependent(const OrderedPathSet& pathSet1, const OrderedPathSet& pathSet2);
+
+/**
+ * Return true if any of the fieldPaths in prefixCandidates are identical to or an ancestor of any
+ * of the fieldpaths in testSet. The order of the parameters matters -- it's not commutative.
+ */
+bool containsDependency(const OrderedPathSet& testSet, const OrderedPathSet& prefixCandidates);
/**
* Determine if 'expr' is reliant upon any path from 'pathSet'.
*/
-bool isIndependentOf(const MatchExpression& expr, const std::set<std::string>& pathSet);
+bool isIndependentOf(const MatchExpression& expr, const OrderedPathSet& pathSet);
/**
* Determine if 'expr' is reliant only upon paths from 'pathSet'.
*/
-bool isOnlyDependentOn(const MatchExpression& expr, const std::set<std::string>& pathSet);
+bool isOnlyDependentOn(const MatchExpression& expr, const OrderedPathSet& pathSet);
/**
* Returns whether the path represented by 'first' is an prefix of the path represented by 'second'.
@@ -122,8 +143,7 @@ bool bidirectionalPathPrefixOf(StringData first, StringData second);
*/
void mapOver(MatchExpression* expr, NodeTraversalFunc func, std::string path = "");
-using ShouldSplitExprFunc =
- std::function<bool(const MatchExpression&, const std::set<std::string>&)>;
+using ShouldSplitExprFunc = std::function<bool(const MatchExpression&, const OrderedPathSet&)>;
/**
* Attempt to split 'expr' into two MatchExpressions according to 'func'. 'func' describes the
@@ -146,7 +166,7 @@ using ShouldSplitExprFunc =
*/
std::pair<std::unique_ptr<MatchExpression>, std::unique_ptr<MatchExpression>>
splitMatchExpressionBy(std::unique_ptr<MatchExpression> expr,
- const std::set<std::string>& fields,
+ const OrderedPathSet& fields,
const StringMap<std::string>& renames,
ShouldSplitExprFunc func = isIndependentOf);
@@ -155,6 +175,8 @@ splitMatchExpressionBy(std::unique_ptr<MatchExpression> expr,
* to the new values of those paths. For example, suppose the original match expression is
* {old: {$gt: 3}} and 'renames' contains the mapping "old" => "new". At the end, 'expr' will be
* {new: {$gt: 3}}.
+ *
+ * The caller should make sure that `expr` is renamable as a whole.
*/
void applyRenamesToExpression(MatchExpression* expr, const StringMap<std::string>& renames);
diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp
index 8679c759cd5..3e0922bd9fc 100644
--- a/src/mongo/db/matcher/expression_algo_test.cpp
+++ b/src/mongo/db/matcher/expression_algo_test.cpp
@@ -181,6 +181,14 @@ TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) {
ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
}
+TEST(ExpressionAlgoIsSubsetOf, CompareAnd_SingleField) {
+ ParsedMatchExpression filter("{a: {$gt: 5, $lt: 7}}");
+ ParsedMatchExpression query("{a: {$gt: 5, $lt: 6}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
+}
+
TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) {
ParsedMatchExpression lt5("{a: {$lt: 5}}");
ParsedMatchExpression eq2OrEq3("{$or: [{a: 2}, {a: 3}]}");
@@ -931,6 +939,15 @@ TEST(IsIndependent, NonRenameableExpressionIsNotIndependent) {
}
}
+TEST(IsIndependent, EmptyDependencySetsPassIsOnlyDependentOn) {
+ BSONObj matchPredicate = fromjson("{}");
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, std::move(expCtx));
+ ASSERT_OK(swMatchExpression.getStatus());
+ auto matchExpression = std::move(swMatchExpression.getValue());
+ ASSERT_TRUE(expression::isOnlyDependentOn(*matchExpression.get(), {}));
+}
+
TEST(SplitMatchExpression, AndWithSplittableChildrenIsSplittable) {
BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 1}]}");
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
@@ -1368,6 +1385,31 @@ TEST(SplitMatchExpression, ShouldMoveIndependentPredicateWhenThereAreMultipleRen
ASSERT_FALSE(splitExpr.second.get());
}
+TEST(SplitMatchExpression, ShouldNotSplitWhenRand) {
+ const auto randExpr = "{$expr: {$lt: [{$rand: {}}, {$const: 0.25}]}}";
+ const auto assertMatchDoesNotSplit = [&](const std::string& exprString) {
+ BSONObj matchPredicate = fromjson(exprString);
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto matcher = MatchExpressionParser::parse(matchPredicate, std::move(expCtx));
+ ASSERT_OK(matcher.getStatus());
+
+ auto&& [split, residual] =
+ expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, {});
+ ASSERT_FALSE(split.get());
+ ASSERT_TRUE(residual.get());
+
+ BSONObjBuilder oldBob;
+ residual->serialize(&oldBob, true);
+ ASSERT_BSONOBJ_EQ(oldBob.obj(), fromjson(randExpr));
+ };
+
+ // We should not push down a $match with a $rand expression.
+ assertMatchDoesNotSplit(randExpr);
+
+ // This is equivalent to 'randExpr'.
+ assertMatchDoesNotSplit("{$sampleRate: 0.25}");
+}
+
TEST(ApplyRenamesToExpression, ShouldApplyBasicRenamesForAMatchWithExpr) {
BSONObj matchPredicate = fromjson("{$expr: {$eq: ['$a.b', '$c']}}");
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
diff --git a/src/mongo/db/matcher/expression_internal_bucket_geo_within.h b/src/mongo/db/matcher/expression_internal_bucket_geo_within.h
index b46b6c5076c..d2a6202744f 100644
--- a/src/mongo/db/matcher/expression_internal_bucket_geo_within.h
+++ b/src/mongo/db/matcher/expression_internal_bucket_geo_within.h
@@ -70,6 +70,7 @@ public:
: MatchExpression(MatchExpression::INTERNAL_BUCKET_GEO_WITHIN, std::move(annotation)),
_geoContainer(container),
_indexField("data." + field),
+ _fieldRef(_indexField),
_field(std::move(field)) {}
void debugString(StringBuilder& debug, int indentationLevel) const final;
@@ -127,8 +128,7 @@ public:
}
const FieldRef* fieldRef() const final {
- MONGO_UNREACHABLE_TASSERT(5837104);
- return nullptr;
+ return &_fieldRef;
}
void acceptVisitor(MatchExpressionMutableVisitor* visitor) final {
@@ -155,6 +155,7 @@ private:
std::shared_ptr<GeometryContainer> _geoContainer;
std::string _indexField;
+ FieldRef _fieldRef;
std::string _field;
};
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index 31157666e92..990f91c1796 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -434,6 +434,7 @@ std::unique_ptr<MatchExpression> InMatchExpression::shallowClone() const {
}
next->_hasNull = _hasNull;
next->_hasEmptyArray = _hasEmptyArray;
+ next->_hasNonEmptyArrayOrObject = _hasNonEmptyArrayOrObject;
next->_equalitySet = _equalitySet;
next->_originalEqualityVector = _originalEqualityVector;
next->_equalityStorage = _equalityStorage;
@@ -577,6 +578,8 @@ Status InMatchExpression::setEqualities(std::vector<BSONElement> equalities) {
_hasNull = true;
} else if (equality.type() == BSONType::Array && equality.Obj().isEmpty()) {
_hasEmptyArray = true;
+ } else if (equality.type() == BSONType::Array || equality.type() == BSONType::Object) {
+ _hasNonEmptyArrayOrObject = true;
}
}
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index 46a80aa5e91..a00ed9e31f1 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -723,6 +723,14 @@ public:
return _hasEmptyArray;
}
+ bool hasNonEmptyArrayOrObject() const {
+ return _hasNonEmptyArrayOrObject;
+ }
+
+ bool hasNonScalarOrNonEmptyValues() const {
+ return hasNonEmptyArrayOrObject() || hasNull() || hasRegex();
+ }
+
void acceptVisitor(MatchExpressionMutableVisitor* visitor) final {
visitor->visit(this);
}
@@ -748,6 +756,9 @@ private:
// Whether or not '_equalities' has an empty array element in it.
bool _hasEmptyArray = false;
+ // Whether or not '_equalities' has a non-empty array or object element in it.
+ bool _hasNonEmptyArrayOrObject = false;
+
// Collator used to construct '_eltCmp';
const CollatorInterface* _collator = nullptr;
diff --git a/src/mongo/db/matcher/expression_optimize_test.cpp b/src/mongo/db/matcher/expression_optimize_test.cpp
index eaa606e06df..04d2bc29e11 100644
--- a/src/mongo/db/matcher/expression_optimize_test.cpp
+++ b/src/mongo/db/matcher/expression_optimize_test.cpp
@@ -495,5 +495,26 @@ TEST(ExpressionOptimizeTest, OrRewrittenToIn) {
ASSERT_BSONOBJ_EQ(optimizeExpr(queries[10].first), fromjson(queries[10].second));
}
+TEST(ExpressionOptimizeTest, NorRemovesAlwaysFalseChildren) {
+ BSONObj obj = fromjson("{$nor: [{a: 1}, {$alwaysFalse: 1}]}");
+ std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj));
+ auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression));
+ ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{a: {$not: {$eq: 1}}}"));
+}
+
+TEST(ExpressionOptimizeTest, NorWithoutChildrenOptimizesToEmptyAnd) {
+ BSONObj obj = fromjson("{$nor: [{$alwaysFalse: 1}, {$alwaysFalse: 1}]}");
+ std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj));
+ auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression));
+ ASSERT_TRUE(dynamic_cast<AndMatchExpression*>(optimizedMatchExpression.get()));
+ ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{}"));
+}
+
+TEST(ExpressionOptimizeTest, NorWithAlwaysTrueChildOptimizesToAlwaysFalse) {
+ BSONObj obj = fromjson("{$nor: [{a: 1}, {$alwaysTrue: 1}]}");
+ std::unique_ptr<MatchExpression> matchExpression(parseMatchExpression(obj));
+ auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression));
+ ASSERT_BSONOBJ_EQ(optimizedMatchExpression->serialize(), fromjson("{$alwaysFalse: 1}"));
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 0e86cd4e06b..e6529908910 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -2205,15 +2205,15 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(MatchExpressionCounters,
if (name[0] == '_' || exceptionsSet.count(name) > 0) {
continue;
}
- operatorCountersMatchExpressions.addMatchExprCounter("$" + name);
+ operatorCountersMatchExpressions.addCounter("$" + name);
}
for (auto&& [name, fn] : *pathlessOperatorMap) {
if (name[0] == '_' || exceptionsSet.count(name) > 0) {
continue;
}
- operatorCountersMatchExpressions.addMatchExprCounter("$" + name);
+ operatorCountersMatchExpressions.addCounter("$" + name);
}
- operatorCountersMatchExpressions.addMatchExprCounter("$not");
+ operatorCountersMatchExpressions.addCounter("$not");
}
diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp
index df70ad278c3..d95f347b50d 100644
--- a/src/mongo/db/matcher/expression_tree.cpp
+++ b/src/mongo/db/matcher/expression_tree.cpp
@@ -97,12 +97,13 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c
std::back_inserter(children));
}
- // Remove all children of AND that are $alwaysTrue and all children of OR that are
+ // Remove all children of AND that are $alwaysTrue and all children of OR and NOR that are
// $alwaysFalse.
- if (matchType == AND || matchType == OR) {
+ if (matchType == AND || matchType == OR || matchType == NOR) {
for (auto& childExpression : children)
if ((childExpression->isTriviallyTrue() && matchType == MatchExpression::AND) ||
- (childExpression->isTriviallyFalse() && matchType == MatchExpression::OR))
+ (childExpression->isTriviallyFalse() && matchType == MatchExpression::OR) ||
+ (childExpression->isTriviallyFalse() && matchType == MatchExpression::NOR))
childExpression = nullptr;
// We replaced each destroyed child expression with nullptr. Now we remove those
@@ -112,13 +113,17 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c
// Check if the above optimizations eliminated all children. An OR with no children is
// always false.
- // TODO SERVER-34759 It is correct to replace this empty AND with an $alwaysTrue, but we
- // need to make enhancements to the planner to make it understand an $alwaysTrue and an
- // empty AND as the same thing. The planner can create inferior plans for $alwaysTrue which
- // it would not produce for an AND with no children.
if (children.empty() && matchType == MatchExpression::OR) {
return std::make_unique<AlwaysFalseMatchExpression>();
}
+ // An AND with no children is always true and we need to return an
+ // EmptyExpression. This ensures that the empty $and[] will be returned that serializes to
+ // {} (SERVER-34759). A NOR with no children is always true. We treat an empty $nor[]
+ // similarly.
+ if (children.empty() &&
+ (matchType == MatchExpression::AND || matchType == MatchExpression::NOR)) {
+ return std::make_unique<AndMatchExpression>();
+ }
if (children.size() == 1) {
if ((matchType == AND || matchType == OR || matchType == INTERNAL_SCHEMA_XOR)) {
@@ -136,7 +141,8 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c
}
}
- if (matchType == MatchExpression::AND || matchType == MatchExpression::OR) {
+ if (matchType == MatchExpression::AND || matchType == MatchExpression::OR ||
+ matchType == MatchExpression::NOR) {
for (auto& childExpression : children) {
// An AND containing an expression that always evaluates to false can be
// optimized to a single $alwaysFalse expression.
@@ -149,6 +155,11 @@ MatchExpression::ExpressionOptimizerFunc ListOfMatchExpression::getOptimizer() c
if (childExpression->isTriviallyTrue() && matchType == MatchExpression::OR) {
return std::make_unique<AlwaysTrueMatchExpression>();
}
+ // A NOR containing an expression that always evaluates to true can be
+ // optimized to a single $alwaysFalse expression.
+ if (childExpression->isTriviallyTrue() && matchType == MatchExpression::NOR) {
+ return std::make_unique<AlwaysFalseMatchExpression>();
+ }
}
}