summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp27
-rw-r--r--src/mongo/db/matcher/expression_leaf.h28
-rw-r--r--src/mongo/db/matcher/expression_leaf_test.cpp51
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp20
-rw-r--r--src/mongo/db/matcher/expression_parser_leaf_test.cpp49
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp12
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp27
7 files changed, 165 insertions, 49 deletions
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index e79b488e894..f826b95ecb3 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -391,6 +391,8 @@ bool ExistsMatchExpression::equivalent(const MatchExpression* other) const {
// ----
+const std::string TypeMatchExpression::kMatchesAllNumbersAlias = "number";
+
const std::unordered_map<std::string, BSONType> TypeMatchExpression::typeAliasMap = {
{"double", NumberDouble},
{"string", String},
@@ -413,13 +415,23 @@ const std::unordered_map<std::string, BSONType> TypeMatchExpression::typeAliasMa
{"maxKey", MaxKey},
{"minKey", MinKey}};
-Status TypeMatchExpression::init(StringData path, BSONType typeInt) {
+Status TypeMatchExpression::initWithBSONType(StringData path, BSONType type) {
_path = path;
- _type = typeInt;
+ _type = type;
+ return _elementPath.init(_path);
+}
+
+Status TypeMatchExpression::initAsMatchingAllNumbers(StringData path) {
+ _path = path;
+ _matchesAllNumbers = true;
return _elementPath.init(_path);
}
bool TypeMatchExpression::matchesSingleElement(const BSONElement& e) const {
+ if (_matchesAllNumbers) {
+ return e.isNumber();
+ }
+
return e.type() == _type;
}
@@ -475,7 +487,16 @@ bool TypeMatchExpression::equivalent(const MatchExpression* other) const {
return false;
const TypeMatchExpression* realOther = static_cast<const TypeMatchExpression*>(other);
- return _path == realOther->_path && _type == realOther->_type;
+
+ if (_path != realOther->_path) {
+ return false;
+ }
+
+ if (_matchesAllNumbers) {
+ return realOther->_matchesAllNumbers;
+ }
+
+ return _type == realOther->_type;
}
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index 2904a72e148..5de55766b44 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -389,15 +389,28 @@ private:
*/
class TypeMatchExpression : public MatchExpression {
public:
+ static const std::string kMatchesAllNumbersAlias;
static const std::unordered_map<std::string, BSONType> typeAliasMap;
TypeMatchExpression() : MatchExpression(TYPE_OPERATOR) {}
- Status init(StringData path, BSONType typeInt);
+ /**
+ * Initialize as matching against a specific BSONType.
+ */
+ Status initWithBSONType(StringData path, BSONType type);
+
+ /**
+ * Initialize as matching against all number types (NumberDouble, NumberLong, and NumberInt).
+ */
+ Status initAsMatchingAllNumbers(StringData path);
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<TypeMatchExpression> e = stdx::make_unique<TypeMatchExpression>();
- e->init(_path, _type);
+ if (_matchesAllNumbers) {
+ e->initAsMatchingAllNumbers(_path);
+ } else {
+ e->initWithBSONType(_path, _type);
+ }
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -417,10 +430,18 @@ public:
/**
* What is the type we're matching against?
*/
- int getData() const {
+ BSONType getType() const {
return _type;
}
+ /**
+ * Whether or not to match against all number types (NumberDouble, NumberLong, and NumberInt).
+ * Defaults to false. If this is true, _type is undefined.
+ */
+ bool matchesAllNumbers() const {
+ return _matchesAllNumbers;
+ }
+
virtual const StringData path() const {
return _path;
}
@@ -430,6 +451,7 @@ private:
StringData _path;
ElementPath _elementPath;
+ bool _matchesAllNumbers = false;
BSONType _type;
};
diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp
index c7f2b447797..ff3f65efb34 100644
--- a/src/mongo/db/matcher/expression_leaf_test.cpp
+++ b/src/mongo/db/matcher/expression_leaf_test.cpp
@@ -1339,7 +1339,7 @@ TEST(TypeMatchExpression, MatchesElementStringType) {
<< "abc");
BSONObj notMatch = BSON("a" << 5);
TypeMatchExpression type;
- ASSERT(type.init("", String).isOK());
+ ASSERT(type.initWithBSONType("", String).isOK());
ASSERT(type.matchesSingleElement(match["a"]));
ASSERT(!type.matchesSingleElement(notMatch["a"]));
}
@@ -1349,11 +1349,30 @@ TEST(TypeMatchExpression, MatchesElementNullType) {
BSONObj notMatch = BSON("a"
<< "abc");
TypeMatchExpression type;
- ASSERT(type.init("", jstNULL).isOK());
+ ASSERT(type.initWithBSONType("", jstNULL).isOK());
ASSERT(type.matchesSingleElement(match["a"]));
ASSERT(!type.matchesSingleElement(notMatch["a"]));
}
+TEST(TypeMatchExpression, MatchesElementNumber) {
+ BSONObj match1 = BSON("a" << 1);
+ BSONObj match2 = BSON("a" << 1LL);
+ BSONObj match3 = BSON("a" << 2.5);
+ BSONObj notMatch = BSON("a"
+ << "abc");
+ ASSERT_EQ(BSONType::NumberInt, match1["a"].type());
+ ASSERT_EQ(BSONType::NumberLong, match2["a"].type());
+ ASSERT_EQ(BSONType::NumberDouble, match3["a"].type());
+
+ TypeMatchExpression type;
+ ASSERT_OK(type.initAsMatchingAllNumbers("a"));
+ ASSERT_EQ("a", type.path());
+ ASSERT_TRUE(type.matchesSingleElement(match1["a"]));
+ ASSERT_TRUE(type.matchesSingleElement(match2["a"]));
+ ASSERT_TRUE(type.matchesSingleElement(match3["a"]));
+ ASSERT_FALSE(type.matchesSingleElement(notMatch["a"]));
+}
+
TEST(TypeMatchExpression, InvalidTypeMatchExpressionerand) {
// If the provided type number is not a valid BSONType, it is not a parse error. The
// operator will simply not match anything.
@@ -1361,21 +1380,21 @@ TEST(TypeMatchExpression, InvalidTypeMatchExpressionerand) {
BSONObj notMatch2 = BSON("a"
<< "abc");
TypeMatchExpression type;
- ASSERT(type.init("", BSONType(JSTypeMax + 1)).isOK());
+ ASSERT(type.initWithBSONType("", BSONType(JSTypeMax + 1)).isOK());
ASSERT(!type.matchesSingleElement(notMatch1["a"]));
ASSERT(!type.matchesSingleElement(notMatch2["a"]));
}
TEST(TypeMatchExpression, MatchesScalar) {
TypeMatchExpression type;
- ASSERT(type.init("a", Bool).isOK());
+ ASSERT(type.initWithBSONType("a", Bool).isOK());
ASSERT(type.matchesBSON(BSON("a" << true), NULL));
ASSERT(!type.matchesBSON(BSON("a" << 1), NULL));
}
TEST(TypeMatchExpression, MatchesArray) {
TypeMatchExpression type;
- ASSERT(type.init("a", NumberInt).isOK());
+ ASSERT(type.initWithBSONType("a", NumberInt).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL));
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(4 << "a")), NULL));
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY("a" << 4)), NULL));
@@ -1385,7 +1404,7 @@ TEST(TypeMatchExpression, MatchesArray) {
TEST(TypeMatchExpression, MatchesOuterArray) {
TypeMatchExpression type;
- ASSERT(type.init("a", Array).isOK());
+ ASSERT(type.initWithBSONType("a", Array).isOK());
// The outer array is not matched.
ASSERT(!type.matchesBSON(BSON("a" << BSONArray()), NULL));
ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(4 << "a")), NULL));
@@ -1397,42 +1416,42 @@ TEST(TypeMatchExpression, MatchesOuterArray) {
TEST(TypeMatchExpression, MatchesObject) {
TypeMatchExpression type;
- ASSERT(type.init("a", Object).isOK());
+ ASSERT(type.initWithBSONType("a", Object).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON("b" << 1)), NULL));
ASSERT(!type.matchesBSON(BSON("a" << 1), NULL));
}
TEST(TypeMatchExpression, MatchesDotNotationFieldObject) {
TypeMatchExpression type;
- ASSERT(type.init("a.b", Object).isOK());
+ ASSERT(type.initWithBSONType("a.b", Object).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON("b" << BSON("c" << 1))), NULL));
ASSERT(!type.matchesBSON(BSON("a" << BSON("b" << 1)), NULL));
}
TEST(TypeMatchExpression, MatchesDotNotationArrayElementArray) {
TypeMatchExpression type;
- ASSERT(type.init("a.0", Array).isOK());
+ ASSERT(type.initWithBSONType("a.0", Array).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(1))), NULL));
ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY("b")), NULL));
}
TEST(TypeMatchExpression, MatchesDotNotationArrayElementScalar) {
TypeMatchExpression type;
- ASSERT(type.init("a.0", String).isOK());
+ ASSERT(type.initWithBSONType("a.0", String).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY("b")), NULL));
ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL));
}
TEST(TypeMatchExpression, MatchesDotNotationArrayElementObject) {
TypeMatchExpression type;
- ASSERT(type.init("a.0", Object).isOK());
+ ASSERT(type.initWithBSONType("a.0", Object).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 1))), NULL));
ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL));
}
TEST(TypeMatchExpression, MatchesNull) {
TypeMatchExpression type;
- ASSERT(type.init("a", jstNULL).isOK());
+ ASSERT(type.initWithBSONType("a", jstNULL).isOK());
ASSERT(type.matchesBSON(BSON("a" << BSONNULL), NULL));
ASSERT(!type.matchesBSON(BSON("a" << 4), NULL));
ASSERT(!type.matchesBSON(BSONObj(), NULL));
@@ -1440,7 +1459,7 @@ TEST(TypeMatchExpression, MatchesNull) {
TEST(TypeMatchExpression, ElemMatchKey) {
TypeMatchExpression type;
- ASSERT(type.init("a.b", String).isOK());
+ ASSERT(type.initWithBSONType("a.b", String).isOK());
MatchDetails details;
details.requestElemMatchKey();
ASSERT(!type.matchesBSON(BSON("a" << 1), &details));
@@ -1462,9 +1481,9 @@ TEST(TypeMatchExpression, Equivalent) {
TypeMatchExpression e1;
TypeMatchExpression e2;
TypeMatchExpression e3;
- e1.init("a", String);
- e2.init("a", NumberDouble);
- e3.init("b", String);
+ e1.initWithBSONType("a", String);
+ e2.initWithBSONType("a", NumberDouble);
+ e3.initWithBSONType("b", String);
ASSERT(e1.equivalent(&e1));
ASSERT(!e1.equivalent(&e2));
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 2f27c157f9c..46a0c8520cf 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -598,8 +598,8 @@ Status MatchExpressionParser::_parseArrayFilterEntries(ArrayFilterEntries* entri
StatusWithMatchExpression MatchExpressionParser::_parseType(const char* name,
const BSONElement& elt) {
- if (!elt.isNumber() && elt.type() != String) {
- return {Status(ErrorCodes::TypeMismatch, "argument to $type is not a number or a string")};
+ if (!elt.isNumber() && elt.type() != BSONType::String) {
+ return Status(ErrorCodes::TypeMismatch, "argument to $type is not a number or a string");
}
std::unique_ptr<TypeMatchExpression> temp = stdx::make_unique<TypeMatchExpression>();
@@ -609,25 +609,35 @@ StatusWithMatchExpression MatchExpressionParser::_parseType(const char* name,
// The element can be a number (the BSON type number) or a string representing the name
// of the type.
if (elt.isNumber()) {
- typeInt = (BSONType)elt.numberInt();
+ typeInt = static_cast<BSONType>(elt.numberInt());
if (elt.type() != NumberInt && typeInt != elt.number()) {
typeInt = static_cast<BSONType>(-1);
}
} else {
+ invariant(elt.type() == BSONType::String);
std::string typeAlias = elt.str();
+ // If typeAlias is 'number', initialize as matching against all number types.
+ if (typeAlias == TypeMatchExpression::kMatchesAllNumbersAlias) {
+ Status s = temp->initAsMatchingAllNumbers(name);
+ if (!s.isOK()) {
+ return s;
+ }
+ return {std::move(temp)};
+ }
+
// Search the string-int map for the typeAlias (case-sensitive).
std::unordered_map<std::string, BSONType>::const_iterator it =
TypeMatchExpression::typeAliasMap.find(typeAlias);
if (it == TypeMatchExpression::typeAliasMap.end()) {
std::stringstream ss;
ss << "unknown string alias for $type: " << typeAlias;
- return {Status(ErrorCodes::BadValue, ss.str())};
+ return Status(ErrorCodes::BadValue, ss.str());
}
typeInt = it->second;
}
- Status s = temp->init(name, typeInt);
+ Status s = temp->initWithBSONType(name, typeInt);
if (!s.isOK()) {
return s;
}
diff --git a/src/mongo/db/matcher/expression_parser_leaf_test.cpp b/src/mongo/db/matcher/expression_parser_leaf_test.cpp
index 57e259bd73b..2d1dd80cc26 100644
--- a/src/mongo/db/matcher/expression_parser_leaf_test.cpp
+++ b/src/mongo/db/matcher/expression_parser_leaf_test.cpp
@@ -649,10 +649,10 @@ TEST(MatchExpressionParserLeafTest, TypeBadString) {
TEST(MatchExpressionParserLeafTest, TypeStringnameDouble) {
StatusWithMatchExpression typeNumberDouble =
MatchExpressionParser::parse(fromjson("{a: {$type: 'double'}}"));
- ASSERT(typeNumberDouble.isOK());
+ ASSERT_OK(typeNumberDouble.getStatus());
TypeMatchExpression* tmeNumberDouble =
static_cast<TypeMatchExpression*>(typeNumberDouble.getValue().get());
- ASSERT(tmeNumberDouble->getData() == NumberDouble);
+ ASSERT(tmeNumberDouble->getType() == NumberDouble);
ASSERT_TRUE(tmeNumberDouble->matchesBSON(fromjson("{a: 5.4}")));
ASSERT_FALSE(tmeNumberDouble->matchesBSON(fromjson("{a: NumberInt(5)}")));
}
@@ -660,10 +660,10 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameDouble) {
TEST(MatchExpressionParserLeafTest, TypeStringnameNumberInt) {
StatusWithMatchExpression typeNumberInt =
MatchExpressionParser::parse(fromjson("{a: {$type: 'int'}}"));
- ASSERT(typeNumberInt.isOK());
+ ASSERT_OK(typeNumberInt.getStatus());
TypeMatchExpression* tmeNumberInt =
static_cast<TypeMatchExpression*>(typeNumberInt.getValue().get());
- ASSERT(tmeNumberInt->getData() == NumberInt);
+ ASSERT(tmeNumberInt->getType() == NumberInt);
ASSERT_TRUE(tmeNumberInt->matchesBSON(fromjson("{a: NumberInt(5)}")));
ASSERT_FALSE(tmeNumberInt->matchesBSON(fromjson("{a: 5.4}")));
}
@@ -671,20 +671,20 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameNumberInt) {
TEST(MatchExpressionParserLeafTest, TypeStringnameNumberLong) {
StatusWithMatchExpression typeNumberLong =
MatchExpressionParser::parse(fromjson("{a: {$type: 'long'}}"));
- ASSERT(typeNumberLong.isOK());
+ ASSERT_OK(typeNumberLong.getStatus());
TypeMatchExpression* tmeNumberLong =
static_cast<TypeMatchExpression*>(typeNumberLong.getValue().get());
- ASSERT(tmeNumberLong->getData() == NumberLong);
- ASSERT_TRUE(tmeNumberLong->matchesBSON(BSON("a" << static_cast<long long>(-1))));
+ ASSERT(tmeNumberLong->getType() == NumberLong);
+ ASSERT_TRUE(tmeNumberLong->matchesBSON(BSON("a" << -1LL)));
ASSERT_FALSE(tmeNumberLong->matchesBSON(fromjson("{a: true}")));
}
TEST(MatchExpressionParserLeafTest, TypeStringnameString) {
StatusWithMatchExpression typeString =
MatchExpressionParser::parse(fromjson("{a: {$type: 'string'}}"));
- ASSERT(typeString.isOK());
+ ASSERT_OK(typeString.getStatus());
TypeMatchExpression* tmeString = static_cast<TypeMatchExpression*>(typeString.getValue().get());
- ASSERT(tmeString->getData() == String);
+ ASSERT(tmeString->getType() == String);
ASSERT_TRUE(tmeString->matchesBSON(fromjson("{a: 'hello world'}")));
ASSERT_FALSE(tmeString->matchesBSON(fromjson("{a: 5.4}")));
}
@@ -692,9 +692,9 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameString) {
TEST(MatchExpressionParserLeafTest, TypeStringnamejstOID) {
StatusWithMatchExpression typejstOID =
MatchExpressionParser::parse(fromjson("{a: {$type: 'objectId'}}"));
- ASSERT(typejstOID.isOK());
+ ASSERT_OK(typejstOID.getStatus());
TypeMatchExpression* tmejstOID = static_cast<TypeMatchExpression*>(typejstOID.getValue().get());
- ASSERT(tmejstOID->getData() == jstOID);
+ ASSERT(tmejstOID->getType() == jstOID);
ASSERT_TRUE(tmejstOID->matchesBSON(fromjson("{a: ObjectId('000000000000000000000000')}")));
ASSERT_FALSE(tmejstOID->matchesBSON(fromjson("{a: 'hello world'}")));
}
@@ -702,10 +702,10 @@ TEST(MatchExpressionParserLeafTest, TypeStringnamejstOID) {
TEST(MatchExpressionParserLeafTest, TypeStringnamejstNULL) {
StatusWithMatchExpression typejstNULL =
MatchExpressionParser::parse(fromjson("{a: {$type: 'null'}}"));
- ASSERT(typejstNULL.isOK());
+ ASSERT_OK(typejstNULL.getStatus());
TypeMatchExpression* tmejstNULL =
static_cast<TypeMatchExpression*>(typejstNULL.getValue().get());
- ASSERT(tmejstNULL->getData() == jstNULL);
+ ASSERT(tmejstNULL->getType() == jstNULL);
ASSERT_TRUE(tmejstNULL->matchesBSON(fromjson("{a: null}")));
ASSERT_FALSE(tmejstNULL->matchesBSON(fromjson("{a: true}")));
}
@@ -713,9 +713,9 @@ TEST(MatchExpressionParserLeafTest, TypeStringnamejstNULL) {
TEST(MatchExpressionParserLeafTest, TypeStringnameBool) {
StatusWithMatchExpression typeBool =
MatchExpressionParser::parse(fromjson("{a: {$type: 'bool'}}"));
- ASSERT(typeBool.isOK());
+ ASSERT_OK(typeBool.getStatus());
TypeMatchExpression* tmeBool = static_cast<TypeMatchExpression*>(typeBool.getValue().get());
- ASSERT(tmeBool->getData() == Bool);
+ ASSERT(tmeBool->getType() == Bool);
ASSERT_TRUE(tmeBool->matchesBSON(fromjson("{a: true}")));
ASSERT_FALSE(tmeBool->matchesBSON(fromjson("{a: null}")));
}
@@ -723,9 +723,9 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameBool) {
TEST(MatchExpressionParserLeafTest, TypeStringnameObject) {
StatusWithMatchExpression typeObject =
MatchExpressionParser::parse(fromjson("{a: {$type: 'object'}}"));
- ASSERT(typeObject.isOK());
+ ASSERT_OK(typeObject.getStatus());
TypeMatchExpression* tmeObject = static_cast<TypeMatchExpression*>(typeObject.getValue().get());
- ASSERT(tmeObject->getData() == Object);
+ ASSERT(tmeObject->getType() == Object);
ASSERT_TRUE(tmeObject->matchesBSON(fromjson("{a: {}}")));
ASSERT_FALSE(tmeObject->matchesBSON(fromjson("{a: []}")));
}
@@ -733,10 +733,21 @@ TEST(MatchExpressionParserLeafTest, TypeStringnameObject) {
TEST(MatchExpressionParserLeafTest, TypeStringnameArray) {
StatusWithMatchExpression typeArray =
MatchExpressionParser::parse(fromjson("{a: {$type: 'array'}}"));
- ASSERT(typeArray.isOK());
+ ASSERT_OK(typeArray.getStatus());
TypeMatchExpression* tmeArray = static_cast<TypeMatchExpression*>(typeArray.getValue().get());
- ASSERT(tmeArray->getData() == Array);
+ ASSERT(tmeArray->getType() == Array);
ASSERT_TRUE(tmeArray->matchesBSON(fromjson("{a: [[]]}")));
ASSERT_FALSE(tmeArray->matchesBSON(fromjson("{a: {}}")));
}
+
+TEST(MatchExpressionParserLeafTest, TypeStringnameNumber) {
+ StatusWithMatchExpression typeNumber =
+ MatchExpressionParser::parse(fromjson("{a: {$type: 'number'}}"));
+ ASSERT_OK(typeNumber.getStatus());
+ TypeMatchExpression* tmeNumber = static_cast<TypeMatchExpression*>(typeNumber.getValue().get());
+ ASSERT_TRUE(tmeNumber->matchesBSON(fromjson("{a: 5.4}")));
+ ASSERT_TRUE(tmeNumber->matchesBSON(fromjson("{a: NumberInt(5)}")));
+ ASSERT_TRUE(tmeNumber->matchesBSON(BSON("a" << -1LL)));
+ ASSERT_FALSE(tmeNumber->matchesBSON(fromjson("{a: ''}")));
+}
}
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index 92bff9f8d39..e32c19eec4d 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -498,13 +498,19 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr,
*tightnessOut = IndexBoundsBuilder::INEXACT_COVERED;
} else if (MatchExpression::TYPE_OPERATOR == expr->matchType()) {
const TypeMatchExpression* tme = static_cast<const TypeMatchExpression*>(expr);
+
+ // If we are matching all numbers, we just use the bounds for NumberInt, as these bounds
+ // also include all NumberDouble and NumberLong values.
+ BSONType type = tme->matchesAllNumbers() ? BSONType::NumberInt : tme->getType();
BSONObjBuilder bob;
- bob.appendMinForType("", tme->getData());
- bob.appendMaxForType("", tme->getData());
+ bob.appendMinForType("", type);
+ bob.appendMaxForType("", type);
BSONObj dataObj = bob.obj();
verify(dataObj.isOwned());
oilOut->intervals.push_back(makeRangeInterval(dataObj, true, true));
- *tightnessOut = IndexBoundsBuilder::INEXACT_FETCH;
+
+ *tightnessOut = tme->matchesAllNumbers() ? IndexBoundsBuilder::EXACT
+ : IndexBoundsBuilder::INEXACT_FETCH;
} else if (MatchExpression::MATCH_IN == expr->matchType()) {
const InMatchExpression* ime = static_cast<const InMatchExpression*>(expr);
const ArrayFilterEntries& afr = ime->getData();
diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp
index 90a509a2577..bd8b9d0d97e 100644
--- a/src/mongo/db/query/index_bounds_builder_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_test.cpp
@@ -622,6 +622,33 @@ TEST(IndexBoundsBuilderTest, TranslateGteBinData) {
}
//
+// $type
+//
+
+TEST(IndexBoundsBuilderTest, TypeNumber) {
+ IndexEntry testIndex = IndexEntry(BSONObj());
+ BSONObj obj = fromjson("{a: {$type: 'number'}}");
+ unique_ptr<MatchExpression> expr(parseMatchExpression(obj));
+ BSONElement elt = obj.firstElement();
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+
+ // Build the expected interval.
+ BSONObjBuilder bob;
+ BSONType type = BSONType::NumberInt;
+ bob.appendMinForType("", type);
+ bob.appendMaxForType("", type);
+ BSONObj expectedInterval = bob.obj();
+
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(expectedInterval, true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+//
// $exists tests
//