diff options
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.h | 28 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf_test.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser_leaf_test.cpp | 49 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_test.cpp | 27 |
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 // |