summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp330
-rw-r--r--src/mongo/db/matcher/expression_algo.h47
-rw-r--r--src/mongo/db/matcher/expression_algo_test.cpp418
-rw-r--r--src/mongo/db/query/get_executor.cpp2
-rw-r--r--src/mongo/db/query/plan_cache_indexability.cpp2
5 files changed, 386 insertions, 413 deletions
diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp
index 9268db2dd00..1e21874a4e1 100644
--- a/src/mongo/db/matcher/expression_algo.cpp
+++ b/src/mongo/db/matcher/expression_algo.cpp
@@ -28,264 +28,176 @@
* it in the license file.
*/
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
-
#include "mongo/platform/basic.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_leaf.h"
-#include "mongo/util/log.h"
-
-//#define DDD(x) log() << x
-#define DDD(x)
namespace mongo {
- namespace {
-
- bool _pathMatches(const LeafMatchExpression* left,
- const MatchExpression* bar) {
- invariant(left);
- invariant(bar);
- const LeafMatchExpression* right = dynamic_cast<const LeafMatchExpression*>(bar);
- if (!right)
- return false;
-
- return left->path() == right->path();
+namespace {
+
+ bool isComparisonMatchExpression(const MatchExpression* expr) {
+ switch (expr->matchType()) {
+ case MatchExpression::LT:
+ case MatchExpression::LTE:
+ case MatchExpression::EQ:
+ case MatchExpression::GTE:
+ case MatchExpression::GT:
+ return true;
+ default:
+ return false;
}
+ }
- bool _typeAndPathCompatable(const ComparisonMatchExpression* left,
- const ComparisonMatchExpression* right) {
- if (!_pathMatches(left, right))
- return false;
-
- if (left->getData().canonicalType() != right->getData().canonicalType())
- return false;
-
+ bool supportsEquality(const ComparisonMatchExpression* expr) {
+ switch (expr->matchType()) {
+ case MatchExpression::LTE:
+ case MatchExpression::EQ:
+ case MatchExpression::GTE:
return true;
+ default:
+ return false;
}
+ }
+ /**
+ * 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.
+ */
+ bool _isSubsetOf(const ComparisonMatchExpression* lhs, const ComparisonMatchExpression* rhs) {
+ // An expression can only be a subset of another if they are comparing the same field.
+ if (lhs->path() != rhs->path()) {
+ return false;
+ }
- bool _isRedundantEQHelp(const ComparisonMatchExpression* left,
- const MatchExpression* bar,
- bool isLessThan,
- bool equalOk) {
- const ComparisonMatchExpression* right =
- dynamic_cast<const ComparisonMatchExpression*>(bar);
- invariant(right);
+ const BSONElement lhsData = lhs->getData();
+ const BSONElement rhsData = rhs->getData();
- if (!_typeAndPathCompatable(left, right))
- return false;
+ if (lhsData.canonicalType() != rhsData.canonicalType()) {
+ return false;
+ }
- int cmp = left->getData().woCompare(right->getData(), false);
- if (isLessThan) {
- if (cmp < 0)
- return true;
- if (cmp == 0)
- return equalOk;
- return false;
+ // Special case the handling for NaN values: NaN compares equal only to itself.
+ if (std::isnan(lhsData.numberDouble()) || std::isnan(rhsData.numberDouble())) {
+ if (supportsEquality(lhs) && supportsEquality(rhs)) {
+ return std::isnan(lhsData.numberDouble()) && std::isnan(rhsData.numberDouble());
}
-
- if (cmp > 0)
- return true;
- if (cmp == 0)
- return equalOk;
return false;
}
- /**
- * @param foo is a literal something that has to match exactly
- * @return if the expression bar guarantees returning any document matching foo
- */
- bool isRedundantEQ(const MatchExpression* foo,
- const MatchExpression* bar) {
- const ComparisonMatchExpression* equal =
- dynamic_cast<const ComparisonMatchExpression*>(foo);
- invariant(equal);
-
- DDD("isRedundantEQ");
+ int cmp = compareElementValues(lhsData, rhsData);
- switch(bar->matchType()) {
- case MatchExpression::EQ:
- // would be handled elsewhere
- return false;
+ // Check whether the two expressions are equivalent.
+ if (lhs->matchType() == rhs->matchType() && cmp == 0) {
+ return true;
+ }
+ switch (rhs->matchType()) {
+ case MatchExpression::LT:
+ case MatchExpression::LTE:
+ switch (lhs->matchType()) {
case MatchExpression::LT:
- return _isRedundantEQHelp(equal, bar, true, false);
case MatchExpression::LTE:
- return _isRedundantEQHelp(equal, bar, true, true);
-
+ case MatchExpression::EQ:
+ if (rhs->matchType() == MatchExpression::LTE) {
+ return cmp <= 0;
+ }
+ return cmp < 0;
+ default:
+ return false;
+ }
+ case MatchExpression::GT:
+ case MatchExpression::GTE:
+ switch (lhs->matchType()) {
case MatchExpression::GT:
- return _isRedundantEQHelp(equal, bar, false, false);
case MatchExpression::GTE:
- return _isRedundantEQHelp(equal, bar, false, true);
-
- case MatchExpression::EXISTS: {
- switch (equal->getData().type()) {
- case jstNULL:
- case Undefined:
- case EOO:
- return false;
- default:
- return _pathMatches(equal, bar);
+ case MatchExpression::EQ:
+ if (rhs->matchType() == MatchExpression::GTE) {
+ return cmp >= 0;
}
- }
-
+ return cmp > 0;
default:
return false;
}
+ default:
+ return false;
}
+ }
- bool isRedundantLT(const MatchExpression* foo,
- const MatchExpression* bar,
- bool equalOk) {
- const ComparisonMatchExpression* left =
- dynamic_cast<const ComparisonMatchExpression*>(foo);
- invariant(left);
-
-
- if(bar->matchType() == MatchExpression::LT ||
- bar->matchType() == MatchExpression::LTE ) {
-
- const ComparisonMatchExpression* right =
- dynamic_cast<const ComparisonMatchExpression*>(bar);
- invariant(right);
-
- if (!_typeAndPathCompatable(left, right))
- return false;
-
- int cmp = left->getData().woCompare(right->getData(), false);
- if (cmp == 0) {
- if(bar->matchType() == MatchExpression::LTE)
- return true;
- if(!equalOk)
- return true;
- return false;
- }
- return cmp < 0;
- }
- else if(bar->matchType() == MatchExpression::EXISTS) {
- return _pathMatches(left, bar);
- }
-
+ /**
+ * 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.
+ */
+ bool _isSubsetOf(const MatchExpression* lhs, const ExistsMatchExpression* rhs) {
+ // An expression can only be a subset of another if they are comparing the same field.
+ if (lhs->path() != rhs->path()) {
return false;
}
- bool isRedundantGT(const MatchExpression* foo,
- const MatchExpression* bar,
- bool equalOk) {
- const ComparisonMatchExpression* left =
- dynamic_cast<const ComparisonMatchExpression*>(foo);
- invariant(left);
-
-
- if(bar->matchType() == MatchExpression::GT ||
- bar->matchType() == MatchExpression::GTE ) {
-
- const ComparisonMatchExpression* right =
- dynamic_cast<const ComparisonMatchExpression*>(bar);
- invariant(right);
-
- if (!_typeAndPathCompatable(left, right))
- return false;
-
- int cmp = left->getData().woCompare(right->getData(), false);
- if (cmp == 0) {
- if(bar->matchType() == MatchExpression::GTE)
- return true;
- if(!equalOk)
- return true;
- return false;
- }
- return cmp > 0;
- }
- else if(bar->matchType() == MatchExpression::EXISTS) {
- return _pathMatches(left, bar);
- }
+ if (lhs->matchType() == MatchExpression::TYPE_OPERATOR) {
+ return true;
+ }
- return false;
+ if (isComparisonMatchExpression(lhs)) {
+ const ComparisonMatchExpression* cme =
+ static_cast<const ComparisonMatchExpression*>(lhs);
+ // CompareMatchExpression::init() prohibits creating a match expression with EOO or
+ // Undefined types, so only need to ensure that the value is not of type jstNULL.
+ return cme->getData().type() != jstNULL;
}
+ // TODO: Add support for using $exists with other query operators.
+ return false;
}
+} // namespace
+namespace expression {
- namespace expression {
-
- bool isClauseRedundant(const MatchExpression* foo,
- const MatchExpression* bar) {
+ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) {
+ invariant(lhs);
+ invariant(rhs);
- if (bar == NULL ||
- foo == bar) {
- return true;
- }
- if (foo == NULL) {
- return false;
- }
-
-
- DDD("isClauseRedundant\n"
- << "foo: " << foo->toString()
- << "bar: " << bar->toString());
-
- if (foo->equivalent(bar)) {
- DDD("t equivalent!");
- return true;
- }
+ if (lhs->equivalent(rhs)) {
+ return true;
+ }
- switch(foo->matchType()) {
- case MatchExpression::AND: {
- for (size_t i = 0; i < foo->numChildren(); i++ ) {
- if(isClauseRedundant(foo->getChild(i), bar)) {
- return true;
- }
+ if (rhs->matchType() == MatchExpression::AND) {
+ // 'lhs' must be a subset of each clause of 'rhs'.
+ for (size_t i = 0; i < rhs->numChildren(); i++) {
+ if (!isSubsetOf(lhs, rhs->getChild(i))) {
+ return false;
}
+ }
+ return true;
+ }
- if (bar->matchType() == MatchExpression::AND) {
- // everything in bar has to appear in foo
- for (size_t i = 0; i < bar->numChildren(); i++ ) {
- if(!isClauseRedundant(foo, bar->getChild(i))) {
- return false;
- }
- }
+ if (lhs->matchType() == MatchExpression::AND) {
+ // At least one clause of 'lhs' must be a subset of 'rhs'.
+ for (size_t i = 0; i < lhs->numChildren(); i++) {
+ if (isSubsetOf(lhs->getChild(i), rhs)) {
return true;
}
-
- return false;
- }
- case MatchExpression::OR: {
- // TODO: $or each clause has to be redundant
- return false;
}
+ return false;
+ }
- case MatchExpression::EQ:
- return isRedundantEQ(foo, bar);
-
- case MatchExpression::LT:
- return isRedundantLT(foo, bar, false);
- case MatchExpression::LTE:
- return isRedundantLT(foo, bar, true);
-
- case MatchExpression::GT:
- return isRedundantGT(foo, bar, false);
- case MatchExpression::GTE:
- return isRedundantGT(foo, bar, true);
-
- case MatchExpression::TYPE_OPERATOR: {
- if (bar->matchType() != MatchExpression::EXISTS) {
- return false;
- }
-
- const TypeMatchExpression* a = dynamic_cast<const TypeMatchExpression*>(foo);
- const ExistsMatchExpression* b = dynamic_cast<const ExistsMatchExpression*>(bar);
-
- return a->path() == b->path();
- }
+ // TODO: Add support for $or in queries.
+ if (lhs->matchType() == MatchExpression::OR) {
+ return false;
+ }
- default:
- return false;
- }
+ if (isComparisonMatchExpression(lhs) && isComparisonMatchExpression(rhs)) {
+ return _isSubsetOf(static_cast<const ComparisonMatchExpression*>(lhs),
+ static_cast<const ComparisonMatchExpression*>(rhs));
+ }
- return false;
+ if (rhs->matchType() == MatchExpression::EXISTS) {
+ return _isSubsetOf(lhs, static_cast<const ExistsMatchExpression*>(rhs));
}
+
+ return false;
}
-}
+
+} // namespace expression
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_algo.h b/src/mongo/db/matcher/expression_algo.h
index 8e3b7bd701c..0ee4459c56f 100644
--- a/src/mongo/db/matcher/expression_algo.h
+++ b/src/mongo/db/matcher/expression_algo.h
@@ -30,21 +30,34 @@
namespace mongo {
- class MatchExpression;
+ class MatchExpression;
- namespace expression {
- /**
- * In filtered index case, a is the query, b is the filter.
- * @return If 'b' cannot reduce the set of returned documents that would
- * be returned with 'a' as the only filter.
- * Everything that matches a also must match b.
- * The document set returned by applying a is a subset of the document
- * set returned by applying b.
- * Examples:
- * a: { x : 5 } b: { x : { $lte : 5 } } = true
- * a: { x : { $lte : 5 } b: { x : 5 } = false
- */
- bool isClauseRedundant(const MatchExpression* a,
- const MatchExpression* b);
- }
-}
+namespace expression {
+
+ /**
+ * 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.
+ *
+ * With respect to partial indexes, 'lhs' corresponds to the query specification and 'rhs'
+ * corresponds to the filter specification.
+ *
+ * e.g.
+ *
+ * Suppose that
+ *
+ * lhs = { x : 4 }
+ * rhs = { x : { $lte : 5 } }
+ *
+ * ==> true
+ *
+ * Suppose that
+ *
+ * lhs = { x : { $gte: 6 } }
+ * rhs = { x : 7 }
+ *
+ * ==> false
+ */
+ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs);
+
+} // namespace expression
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp
index 65309c0d3a3..9e162c6eb7a 100644
--- a/src/mongo/db/matcher/expression_algo_test.cpp
+++ b/src/mongo/db/matcher/expression_algo_test.cpp
@@ -1,7 +1,7 @@
// expression_algo_test.cpp
/**
- * Copyright (C) 2013 10gen Inc.
+ * Copyright (C) 2015 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
@@ -30,7 +30,7 @@
#include "mongo/unittest/unittest.h"
-#include <boost/scoped_ptr.hpp>
+#include <memory>
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
@@ -38,241 +38,289 @@
#include "mongo/db/matcher/expression_algo.h"
#include "mongo/db/matcher/expression_parser.h"
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
-#include "mongo/util/log.h"
-
namespace mongo {
- using boost::scoped_ptr;
-
/**
- * A MatchExpression does not hold the memory for BSONElements.
- * So using this we can tie the life cycle of a MatchExpression to its data.
+ * A MatchExpression does not hold the memory for BSONElements, so use ParsedMatchExpression to
+ * ensure that the BSONObj outlives the MatchExpression.
*/
- struct Parsed {
- Parsed(const char* str) {
- obj = fromjson(str);
- _parse();
+ class ParsedMatchExpression {
+ public:
+ ParsedMatchExpression(const std::string& str)
+ : _obj(fromjson(str)) {
+ StatusWithMatchExpression result = MatchExpressionParser::parse(_obj);
+ ASSERT_OK(result.getStatus());
+ _expr.reset(result.getValue());
}
- Parsed(const BSONObj& o)
- : obj(o) {
- _parse();
- }
+ const MatchExpression* get() const { return _expr.get(); }
- void _parse() {
- StatusWithMatchExpression result = MatchExpressionParser::parse(obj);
- if (!result.isOK()) {
- log() << "failed to parse expression: " << obj;
- invariant(false);
- }
- exp.reset(result.getValue());
- }
+ private:
+ const BSONObj _obj;
+ std::unique_ptr<MatchExpression> _expr;
+ };
- const MatchExpression* get() const { return exp.get(); }
+ TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) {
+ // Verify that ComparisonMatchExpression::init() prohibits creating a match expression with
+ // an Undefined type.
+ BSONObj undefined = fromjson("{a: undefined}");
+ ASSERT_EQUALS(ErrorCodes::BadValue, MatchExpressionParser::parse(undefined).getStatus());
- BSONObj obj;
- scoped_ptr<MatchExpression> exp;
- };
+ ParsedMatchExpression empty("{}");
+ ParsedMatchExpression null("{a: null}");
- TEST(ExpressionAlgoRedundant, Equal1) {
- Parsed foo("{ x : 5 }");
- Parsed bar1("{ x : 5 }");
- Parsed bar2("{ x : 6 }");
- Parsed bar3("{ a : 5 }");
- ASSERT_TRUE(expression::isClauseRedundant(foo.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar3.get()));
- }
+ ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get()));
+ ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get()));
- TEST(ExpressionAlgoRedundant, AndEqual1) {
- Parsed foo("{ a : 3, x : 5 }");
- Parsed bar1("{ x : 5 }");
- Parsed bar2("{ x : 6 }");
- Parsed bar3("{ a : 5 }");
- ASSERT_TRUE(expression::isClauseRedundant(foo.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar3.get()));
- ASSERT_FALSE(expression::isClauseRedundant(bar1.get(), foo.get()));
- }
+ ParsedMatchExpression b1("{b: 1}");
+ ParsedMatchExpression aNullB1("{a: null, b: 1}");
+
+ ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get()));
+ ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get()));
- TEST(ExpressionAlgoRedundant, DifferentTypes1) {
- // Comparison of different canonical types is not redundant.
- Parsed foo("{x: {$gt: \"a\"}}");
- Parsed bar("{x: {$gt: 1}}");
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar.get()));
- ASSERT_FALSE(expression::isClauseRedundant(bar.get(), foo.get()));
+ ParsedMatchExpression a1C3("{a: 1, c: 3}");
+ ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}");
+
+ ASSERT_TRUE(expression::isSubsetOf(a1BNullC3.get(), a1C3.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a1C3.get(), a1BNullC3.get()));
}
- TEST(ExpressionAlgoRedundant, DifferentTypes2) {
- Parsed foo("{x: null}");
- Parsed bar("{x: {$exists: true}}");
- ASSERT_FALSE(expression::isClauseRedundant(foo.get(), bar.get()));
+ TEST(ExpressionAlgoIsSubsetOf, NullAndExists) {
+ ParsedMatchExpression null("{x: null}");
+ ParsedMatchExpression exists("{x: {$exists: true}}");
+ ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get()));
+ ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get()));
}
- TEST(ExpressionAlgoRedundant, PointToRange) {
- Parsed foo1("{ x : 4 }");
- Parsed foo2("{ x : 5 }");
- Parsed foo3("{ a : 4 }");
- Parsed foo4("{ x : 6 }");
- Parsed foo5("{ a : 6 }");
-
- Parsed bar1("{ x : { $lte : 5 } }");
- Parsed bar2("{ x : { $lt : 5 } }");
- Parsed bar3("{ x : { $gte : 5 } }");
- Parsed bar4("{ x : { $gt : 5 } }");
-
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar1.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(bar1.get(), foo1.get()));
-
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo2.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar2.get()));
-
- ASSERT_FALSE(expression::isClauseRedundant(foo1.get(), bar3.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar3.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar3.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo4.get(), bar3.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar3.get()));
-
- ASSERT_FALSE(expression::isClauseRedundant(foo1.get(), bar4.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo2.get(), bar4.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar4.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo4.get(), bar4.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar4.get()));
+ TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) {
+ ParsedMatchExpression nan("{x: NaN}");
+ ParsedMatchExpression lt("{x: {$lt: 5}}");
+ ParsedMatchExpression lte("{x: {$lte: 5}}");
+ ParsedMatchExpression gte("{x: {$gte: 5}}");
+ ParsedMatchExpression gt("{x: {$gt: 5}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get()));
+ ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lt.get(), nan.get()));
+ ASSERT_FALSE(expression::isSubsetOf(nan.get(), lte.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lte.get(), nan.get()));
+ ASSERT_FALSE(expression::isSubsetOf(nan.get(), gte.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gte.get(), nan.get()));
+ ASSERT_FALSE(expression::isSubsetOf(nan.get(), gt.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gt.get(), nan.get()));
}
- TEST(ExpressionAlgoRedundant, LessThanToLessThan) {
- Parsed foo1("{ x : { $lte : 4 } }");
- Parsed foo2("{ x : { $lt : 5 } }");
- Parsed foo3("{ x : { $lte : 5 } }");
- Parsed foo4("{ x : { $lte : 6 } }");
- Parsed foo5("{ a : { $lte : 4 } }");
+ TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) {
+ ParsedMatchExpression a5("{a: 5}");
+ ParsedMatchExpression a6("{a: 6}");
+ ParsedMatchExpression b5("{b: 5}");
- Parsed bar1("{ x : { $lte : 5 } }");
- Parsed bar2("{ x : { $lt : 5 } }");
+ ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a5.get(), b5.get()));
+ }
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar1.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar1.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo3.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar1.get()));
+ TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) {
+ ParsedMatchExpression a1B2("{a: 1, b: 2}");
+ ParsedMatchExpression a1B7("{a: 1, b: 7}");
+ ParsedMatchExpression a1("{a: 1}");
+ ParsedMatchExpression b2("{b: 2}");
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar2.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar2.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), b2.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a1B7.get(), b2.get()));
}
- TEST(ExpressionAlgoRedundant, GreaterThanToGreaterThan) {
- Parsed foo1("{ x : { $gte : 6 } }");
- Parsed foo2("{ x : { $gt : 5 } }");
- Parsed foo3("{ x : { $gte : 5 } }");
- Parsed foo4("{ x : { $gte : 4 } }");
- Parsed foo5("{ a : { $gte : 6 } }");
-
- Parsed bar1("{ x : { $gte : 5 } }");
- Parsed bar2("{ x : { $gt : 5 } }");
+ TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) {
+ ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}");
+ ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}");
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar1.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar1.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo3.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar1.get()));
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
+ }
- ASSERT_TRUE(expression::isClauseRedundant(foo1.get(), bar2.get()));
- ASSERT_TRUE(expression::isClauseRedundant(foo2.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo3.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo4.get(), bar2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(foo5.get(), bar2.get()));
+ TEST(ExpressionAlgoIsSubsetOf, DifferentCanonicalTypes) {
+ ParsedMatchExpression number("{x: {$gt: 1}}");
+ ParsedMatchExpression string("{x: {$gt: 'a'}}");
+ ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get()));
+ ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get()));
+ }
+ TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) {
+ ParsedMatchExpression numberDouble("{x: 5.0}");
+ ParsedMatchExpression numberInt("{x: NumberInt(5)}");
+ ParsedMatchExpression numberLong("{x: NumberLong(5)}");
+
+ ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get()));
+ ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get()));
+ ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberDouble.get()));
+ ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberLong.get()));
+ ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberDouble.get()));
+ ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberInt.get()));
}
- TEST(ExpressionAlgoRedundant, Exists1) {
- Parsed a("{a : { $exists : 1 } }");
- Parsed b("{b : { $exists : 1 } }");
- Parsed ab("{a : { $exists : 1 }, b : { $exists: 1 } }");
- Parsed abc("{a : { $exists : 1 }, b : { $exists: 1 }, c : 5 }");
+ TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) {
+ ParsedMatchExpression a4("{a: 4}");
+ ParsedMatchExpression a5("{a: 5}");
+ ParsedMatchExpression a6("{a: 6}");
+ ParsedMatchExpression b5("{b: 5}");
+
+ ParsedMatchExpression lt5("{a: {$lt: 5}}");
+ ParsedMatchExpression lte5("{a: {$lte: 5}}");
+ ParsedMatchExpression gte5("{a: {$gte: 5}}");
+ ParsedMatchExpression gt5("{a: {$gt: 5}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a6.get(), lte5.get()));
+
+ ASSERT_TRUE(expression::isSubsetOf(a4.get(), lt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a5.get(), lt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a6.get(), lt5.get()));
+
+ ASSERT_FALSE(expression::isSubsetOf(a4.get(), gte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a5.get(), gte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a6.get(), gte5.get()));
+
+ ASSERT_FALSE(expression::isSubsetOf(a4.get(), gt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(a5.get(), gt5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(a6.get(), gt5.get()));
+
+ // An unbounded range query does not match a subset of documents of a point query.
+ ASSERT_FALSE(expression::isSubsetOf(lt5.get(), a5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lte5.get(), a5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gte5.get(), a5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gt5.get(), a5.get()));
+
+ // Cannot be a subset if comparing different field names.
+ ASSERT_FALSE(expression::isSubsetOf(b5.get(), lt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(b5.get(), lte5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(b5.get(), gte5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(b5.get(), gt5.get()));
+ }
- ASSERT_TRUE(expression::isClauseRedundant(a.get(), a.get()));
- ASSERT_FALSE(expression::isClauseRedundant(a.get(), b.get()));
- ASSERT_FALSE(expression::isClauseRedundant(b.get(), a.get()));
+ TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) {
+ ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}");
+ ParsedMatchExpression query("{a: 6}");
- ASSERT_TRUE(expression::isClauseRedundant(ab.get(), a.get()));
- ASSERT_TRUE(expression::isClauseRedundant(ab.get(), b.get()));
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
+ }
- ASSERT_TRUE(expression::isClauseRedundant(abc.get(), a.get()));
- ASSERT_TRUE(expression::isClauseRedundant(abc.get(), b.get()));
+ TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) {
+ ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}");
+ ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}");
- ASSERT_TRUE(expression::isClauseRedundant(abc.get(), ab.get()));
- ASSERT_FALSE(expression::isClauseRedundant(ab.get(), abc.get()));
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
}
- TEST(ExpressionAlgoRedundant, Exists2) {
- Parsed filter("{a : { $exists : 1 } }");
- Parsed query1("{a : 1}");
- Parsed query2("{a : { $gt : 4 } }");
- Parsed query3("{a : { $lt : 4 } }");
+ TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) {
+ ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}");
+ ParsedMatchExpression query("{a: 10, b: 10, c: 10}");
- ASSERT_TRUE(expression::isClauseRedundant(query1.get(), filter.get()));
- ASSERT_TRUE(expression::isClauseRedundant(query2.get(), filter.get()));
- ASSERT_TRUE(expression::isClauseRedundant(query3.get(), filter.get()));
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query1.get()));
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
}
- TEST(ExpressionAlgoRedundant, Exists3) {
- Parsed filter("{a : { $exists : 1 } }");
- Parsed query1("{a : { $type : 5 } }");
+ TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) {
+ ParsedMatchExpression lte4("{x: {$lte: 4}}");
+ ParsedMatchExpression lt5("{x: {$lt: 5}}");
+ ParsedMatchExpression lte5("{x: {$lte: 5}}");
+ ParsedMatchExpression lt6("{x: {$lt: 6}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(lte5.get(), lte5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lte5.get()));
+
+ ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lt5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lte5.get(), lt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lt5.get()));
+ }
- ASSERT_TRUE(expression::isClauseRedundant(query1.get(), filter.get()));
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query1.get()));
+ TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) {
+ ParsedMatchExpression gte6("{x: {$gte: 6}}");
+ ParsedMatchExpression gt5("{x: {$gt: 5}}");
+ ParsedMatchExpression gte5("{x: {$gte: 5}}");
+ ParsedMatchExpression gt4("{x: {$gt: 4}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gte5.get()));
+
+ ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gt5.get()));
+ ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gte5.get(), gt5.get()));
+ ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gt5.get()));
}
- TEST(ExpressionAlgoRedundant, Exists4) {
- Parsed filter("{a : { $exists : 1 } }");
- Parsed query1("{b : { $type : 5 } }");
+ TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) {
+ ParsedMatchExpression filter("{a: {$gt: 1}}");
+ ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}");
- ASSERT_FALSE(expression::isClauseRedundant(query1.get(), filter.get()));
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query1.get()));
+ ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get()));
}
- TEST(ExpressionAlgoRedundant, Type1) {
- Parsed a("{a : { $type : 4 } }");
- Parsed a2("{a : { $type : 4 } }");
- Parsed b("{a : { $type : 7 } }");
+ TEST(ExpressionAlgoIsSubsetOf, Exists) {
+ ParsedMatchExpression aExists("{a: {$exists: true}}");
+ ParsedMatchExpression bExists("{b: {$exists: true}}");
+ ParsedMatchExpression aExistsBExists("{a: {$exists: true}, b: {$exists: true}}");
+ ParsedMatchExpression aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}");
- ASSERT_TRUE(expression::isClauseRedundant(a.get(), a2.get()));
- ASSERT_FALSE(expression::isClauseRedundant(a.get(), b.get()));
- }
+ ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get()));
+ ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get()));
- TEST(ExpressionAlgoRedundant, Subset1) {
- Parsed filter("{ a : 5, b : 6 }");
- Parsed query("{ a : 5, b : 6, c : 7 }");
+ ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), aExists.get()));
+ ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), bExists.get()));
+ ASSERT_FALSE(expression::isSubsetOf(aExistsBExists.get(), aExistsBExistsC5.get()));
- ASSERT_TRUE(expression::isClauseRedundant(query.get(), filter.get()));
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query.get()));
+ ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExists.get()));
+ ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), bExists.get()));
+ ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExistsBExists.get()));
}
- TEST(ExpressionAlgoRedundant, Subset2) {
- Parsed filter("{ a : { $gt : 5 }, b : { $gt : 6 } }");
- Parsed query1("{ a : { $gt : 5 }, b : { $gt : 6 }, c : { $gt : 7 } }");
- Parsed query2("{ a : 10, b : 10, c : 10 }");
+ TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) {
+ ParsedMatchExpression exists("{a: {$exists: true}}");
+ ParsedMatchExpression eq("{a: 1}");
+ ParsedMatchExpression gt("{a: {$gt: 4}}");
+ ParsedMatchExpression lte("{a: {$lte: 7}}");
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query1.get()));
- ASSERT_FALSE(expression::isClauseRedundant(filter.get(), query2.get()));
+ ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get()));
+ ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get()));
+ ASSERT_TRUE(expression::isSubsetOf(lte.get(), exists.get()));
- ASSERT_TRUE(expression::isClauseRedundant(query1.get(), filter.get()));
- ASSERT_TRUE(expression::isClauseRedundant(query2.get(), filter.get()));
+ ASSERT_FALSE(expression::isSubsetOf(exists.get(), eq.get()));
+ ASSERT_FALSE(expression::isSubsetOf(exists.get(), gt.get()));
+ ASSERT_FALSE(expression::isSubsetOf(exists.get(), lte.get()));
+ }
+
+ TEST(ExpressionAlgoIsSubsetOf, Type) {
+ ParsedMatchExpression aType1("{a: {$type: 1}}");
+ ParsedMatchExpression aType2("{a: {$type: 2}}");
+ ParsedMatchExpression bType2("{b: {$type: 2}}");
+
+ ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get()));
+ ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get()));
+ ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aType2.get()));
+ ASSERT_FALSE(expression::isSubsetOf(aType2.get(), bType2.get()));
}
-}
+ TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) {
+ ParsedMatchExpression aExists("{a: {$exists: true}}");
+ ParsedMatchExpression aType2("{a: {$type: 2}}");
+ ParsedMatchExpression bType2("{b: {$type: 2}}");
+
+ ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get()));
+ ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get()));
+ ASSERT_FALSE(expression::isSubsetOf(bType2.get(), aExists.get()));
+ }
+} // namespace mongo
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 183a60ec8af..7a9ad59a693 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -127,7 +127,7 @@ namespace mongo {
return true;
}
- return !expression::isClauseRedundant(queryPredicates, filter);
+ return !expression::isSubsetOf(queryPredicates, filter);
}
} // namespace
diff --git a/src/mongo/db/query/plan_cache_indexability.cpp b/src/mongo/db/query/plan_cache_indexability.cpp
index e7d3577c34d..043ba1d7a40 100644
--- a/src/mongo/db/query/plan_cache_indexability.cpp
+++ b/src/mongo/db/query/plan_cache_indexability.cpp
@@ -64,7 +64,7 @@ namespace mongo {
if (!filterExpr->isLogical()) {
_pathDiscriminatorsMap[filterExpr->path()].push_back(
[filterExpr] (const MatchExpression* queryExpr) {
- return expression::isClauseRedundant(queryExpr, filterExpr);
+ return expression::isSubsetOf(queryExpr, filterExpr);
}
);
}