TEST( MatchExpressionParserLeafTest, NotRegex1 ) { BSONObjBuilder b; b.appendRegex( "$not", "abc", "i" ); BSONObj query = BSON( "x" << b.obj() ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "ABC" ) ) ); ASSERT( result.getValue()->matchesBSON( BSON( "x" << "AC" ) ) ); }
TEST( MatchExpressionParserTreeTest, NOREmbedded ) { BSONObj query = BSON( "$nor" << BSON_ARRAY( BSON( "x" << 1 ) << BSON( "y" << 2 ) ) ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 2 ) ) ); ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); ASSERT( result.getValue()->matchesBSON( BSON( "y" << 1 ) ) ); }
TEST(MatchExpressionParserTreeTest, NOREmbedded) { BSONObj query = BSON("$nor" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("y" << 2))); ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); ASSERT(result.getValue()->matchesBSON(BSON("y" << 1))); }
TEST( MatchExpressionParserTest, Multiple1 ) { BSONObj query = BSON( "x" << 5 << "y" << BSON( "$gt" << 5 << "$lt" << 8 ) ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 7 ) ) ); ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 6 ) ) ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 6 << "y" << 7 ) ) ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 9 ) ) ); ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 4 ) ) ); }
Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool* positional) { // Perform standard field name and updateable checks. _fieldRef.parse(modExpr.fieldName()); Status status = fieldchecker::isUpdatable(_fieldRef); if (! status.isOK()) { return status; } // If a $-positional operator was used, get the index in which it occurred // and ensure only one occurrence. size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (positional) *positional = foundDollar; if (foundDollar && foundCount > 1) { return Status(ErrorCodes::BadValue, str::stream() << "Too many positional (i.e. '$') elements found in path '" << _fieldRef.dottedField() << "'"); } _exprElt = modExpr; // If the element in the mod is actually an object or a regular expression, we need to // build a matcher, instead of just doing an equality comparision. if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) { if (_exprElt.type() == Object) { _exprObj = _exprElt.embeddedObject(); // If not is not a query operator, then it is a primitive. _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0); // If the object is primitive then wrap it up into an object. if (_matcherOnPrimitive) _exprObj = BSON( "" << _exprObj ); } else { // For a regex, we also need to wrap and treat like a primitive. _matcherOnPrimitive = true; _exprObj = _exprElt.wrap(""); } // Build the matcher around the object we built above. StatusWithMatchExpression parseResult = MatchExpressionParser::parse(_exprObj); if (!parseResult.isOK()) return parseResult.getStatus(); _matchExpr.reset(parseResult.getValue()); } return Status::OK(); }
std::unique_ptr<MatchExpression> QueryPlannerTest::parseMatchExpression( const BSONObj& obj, const CollatorInterface* collator) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); expCtx->setCollator(collator); StatusWithMatchExpression status = MatchExpressionParser::parse(obj, std::move(expCtx)); if (!status.isOK()) { FAIL(str::stream() << "failed to parse query: " << obj.toString() << ". Reason: " << status.getStatus().toString()); } return std::move(status.getValue()); }
TEST(MatchExpressionParserTest, Multiple1) { BSONObj query = BSON("x" << 5 << "y" << BSON("$gt" << 5 << "$lt" << 8)); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator); ASSERT_TRUE(result.isOK()); ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 7))); ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 6))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 6 << "y" << 7))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 9))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 4))); }
TEST(MatchExpressionParserTreeTest, NOREmbedded) { BSONObj query = BSON("$nor" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("y" << 2))); ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); ASSERT(result.getValue()->matchesBSON(BSON("y" << 1))); }
void run() { Client::WriteContext ctx(ns()); DurTransaction txn; Database* db = ctx.ctx().db(); Collection* coll = db->getCollection(ns()); if (!coll) { coll = db->createCollection(&txn, ns()); } WorkingSet ws; // Add an object to the DB. insert(BSON("foo" << 5)); set<DiskLoc> locs; getLocs(&locs, coll); ASSERT_EQUALS(size_t(1), locs.size()); // Create a mock stage that returns the WSM. auto_ptr<MockStage> mockStage(new MockStage(&ws)); // Mock data. { WorkingSetMember mockMember; mockMember.state = WorkingSetMember::LOC_AND_IDX; mockMember.loc = *locs.begin(); // State is loc and index, shouldn't be able to get the foo data inside. BSONElement elt; ASSERT_FALSE(mockMember.getFieldDotted("foo", &elt)); mockStage->pushBack(mockMember); } // Make the filter. BSONObj filterObj = BSON("foo" << 6); StatusWithMatchExpression swme = MatchExpressionParser::parse(filterObj); verify(swme.isOK()); auto_ptr<MatchExpression> filterExpr(swme.getValue()); // Matcher requires that foo==6 but we only have data with foo==5. auto_ptr<FetchStage> fetchStage( new FetchStage(&ws, mockStage.release(), filterExpr.get(), coll)); // First call should return a fetch request as it's not in memory. WorkingSetID id = WorkingSet::INVALID_ID; PlanStage::StageState state; // Normally we'd return the object but we have a filter that prevents it. state = fetchStage->work(&id); ASSERT_EQUALS(PlanStage::NEED_TIME, state); // No more data to fetch, so, EOF. state = fetchStage->work(&id); ASSERT_EQUALS(PlanStage::IS_EOF, state); }
TEST(MatchExpressionParserGeo, WithinBox) { BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); StatusWithMatchExpression result = MatchExpressionParser::parse(query); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); }
TEST(MatchExpressionParserGeoNear, ParseInvalidNearSphere) { { BSONObj query = fromjson("{loc: {$maxDistance: 100, $nearSphere: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$minDistance: 100, $nearSphere: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $maxDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $minDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $eq: 1}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } }
// For $near, $nearSphere, and $geoNear syntax of: // { // $near/$nearSphere/$geoNear: [ <x>, <y> ], // $minDistance: <distance in radians>, // $maxDistance: <distance in radians> // } TEST(MatchExpressionParserGeoNear, ParseValidNear) { BSONObj query = fromjson("{loc: {$near: [0,0], $maxDistance: 100, $minDistance: 50}}"); StatusWithMatchExpression result = MatchExpressionParser::parse(query); ASSERT_TRUE(result.isOK()); MatchExpression* exp = result.getValue().get(); ASSERT_EQ(MatchExpression::GEO_NEAR, exp->matchType()); GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); ASSERT_EQ(gnexp->getData().maxDistance, 100); ASSERT_EQ(gnexp->getData().minDistance, 50); }
TEST( MatchExpressionParserText, Parse1 ) { BSONObj query = fromjson( "{$text:{$search:\"awesome\", $language:\"english\"}}" ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); MatchExpression* exp = result.getValue(); ASSERT_EQUALS( MatchExpression::TEXT, exp->matchType() ); TextMatchExpression* textExp = static_cast<TextMatchExpression*>( exp ); ASSERT_EQUALS( textExp->getQuery(), "awesome" ); ASSERT_EQUALS( textExp->getLanguage(), "english" ); }
TEST( MatchExpressionParserGeoNear, ParseNear ) { BSONObj query = fromjson("{loc:{$near:{$maxDistance:100, " "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_TRUE( result.isOK() ); MatchExpression* exp = result.getValue(); ASSERT_EQUALS(MatchExpression::GEO_NEAR, exp->matchType()); GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); ASSERT_EQUALS(gnexp->getData().maxDistance, 100); }
Matcher::Matcher(const BSONObj& pattern, const boost::intrusive_ptr<ExpressionContext>& expCtx, const ExtensionsCallback& extensionsCallback, const MatchExpressionParser::AllowedFeatureSet allowedFeatures) : _pattern(pattern) { StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(pattern, expCtx, extensionsCallback, allowedFeatures); uassert(16810, mongoutils::str::stream() << "bad query: " << statusWithMatcher.getStatus().toString(), statusWithMatcher.isOK()); _expression = std::move(statusWithMatcher.getValue()); }
// static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( const std::string& ns, const BSONObj& query, const BSONObj& sort, const BSONObj& proj, long long skip, long long limit, const BSONObj& hint, const BSONObj& minObj, const BSONObj& maxObj, bool snapshot, bool explain, const MatchExpressionParser::WhereCallback& whereCallback) { // Pass empty sort and projection. BSONObj emptyObj; auto lpqStatus = LiteParsedQuery::makeAsOpQuery(NamespaceString(ns), skip, limit, 0, query, proj, sort, hint, minObj, maxObj, snapshot, explain); if (!lpqStatus.isOK()) { return lpqStatus.getStatus(); } auto& lpq = lpqStatus.getValue(); // Build a parse tree from the BSONObj in the parsed query. StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(lpq->getFilter(), whereCallback); if (!statusWithMatcher.isOK()) { return statusWithMatcher.getStatus(); } std::unique_ptr<MatchExpression> me = std::move(statusWithMatcher.getValue()); // Make the CQ we'll hopefully return. std::unique_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init(lpq.release(), whereCallback, me.release()); if (!initStatus.isOK()) { return initStatus; } return std::move(cq); }
// static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( OperationContext* opCtx, std::unique_ptr<QueryRequest> qr, const boost::intrusive_ptr<ExpressionContext>& expCtx, const ExtensionsCallback& extensionsCallback, MatchExpressionParser::AllowedFeatureSet allowedFeatures) { auto qrStatus = qr->validate(); if (!qrStatus.isOK()) { return qrStatus; } std::unique_ptr<CollatorInterface> collator; if (!qr->getCollation().isEmpty()) { auto statusWithCollator = CollatorFactoryInterface::get(opCtx->getServiceContext()) ->makeFromBSON(qr->getCollation()); if (!statusWithCollator.isOK()) { return statusWithCollator.getStatus(); } collator = std::move(statusWithCollator.getValue()); } // Make MatchExpression. boost::intrusive_ptr<ExpressionContext> newExpCtx; if (!expCtx.get()) { newExpCtx.reset(new ExpressionContext(opCtx, collator.get())); } else { newExpCtx = expCtx; invariant(CollatorInterface::collatorsMatch(collator.get(), expCtx->getCollator())); } StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse( qr->getFilter(), newExpCtx, extensionsCallback, allowedFeatures); if (!statusWithMatcher.isOK()) { return statusWithMatcher.getStatus(); } std::unique_ptr<MatchExpression> me = std::move(statusWithMatcher.getValue()); // Make the CQ we'll hopefully return. std::unique_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init(opCtx, std::move(qr), parsingCanProduceNoopMatchNodes(extensionsCallback, allowedFeatures), std::move(me), std::move(collator)); if (!initStatus.isOK()) { return initStatus; } return std::move(cq); }
TEST(MatchExpressionParserGeo, WithinBox) { BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); }
TEST(MatchExpressionParserLeafTest, NotRegex1) { BSONObjBuilder b; b.appendRegex("$not", "abc", "i"); BSONObj query = BSON("x" << b.obj()); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "abc"))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "ABC"))); ASSERT(result.getValue()->matchesBSON(BSON("x" << "AC"))); }
// For $near, $nearSphere, and $geoNear syntax of: // { // $near/$nearSphere/$geoNear: [ <x>, <y> ], // $minDistance: <distance in radians>, // $maxDistance: <distance in radians> // } TEST(MatchExpressionParserGeoNear, ParseValidNear) { BSONObj query = fromjson("{loc: {$near: [0,0], $maxDistance: 100, $minDistance: 50}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(result.isOK()); MatchExpression* exp = result.getValue().get(); ASSERT_EQ(MatchExpression::GEO_NEAR, exp->matchType()); GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); ASSERT_EQ(gnexp->getData().maxDistance, 100); ASSERT_EQ(gnexp->getData().minDistance, 50); }
TEST(MatchExpressionParserGeoNear, ParseNear) { BSONObj query = fromjson( "{loc:{$near:{$maxDistance:100, " "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(result.isOK()); MatchExpression* exp = result.getValue().get(); ASSERT_EQUALS(MatchExpression::GEO_NEAR, exp->matchType()); GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); ASSERT_EQUALS(gnexp->getData().maxDistance, 100); }
// Test a tree that exceeds the maximum depth limit. TEST( MatchExpressionParserTreeTest, MaximumTreeDepthExceed ) { static const int depth = 105; std::stringstream ss; for (int i = 0; i < depth/2; i++) { ss << "{$and: [{a: 3}, {$or: [{b: 2},"; } ss << "{b: 4}"; for (int i = 0; i < depth/2; i++) { ss << "]}]}"; } BSONObj query = fromjson( ss.str() ); StatusWithMatchExpression result = MatchExpressionParser::parse( query ); ASSERT_FALSE( result.isOK() ); }
int countResults(const IndexScanParams& params, BSONObj filterObj = BSONObj()) { Client::ReadContext ctx(ns()); PlanExecutor runner; StatusWithMatchExpression swme = MatchExpressionParser::parse(filterObj); verify(swme.isOK()); auto_ptr<MatchExpression> filterExpr(swme.getValue()); runner.setRoot(new IndexScan(params, runner.getWorkingSet(), filterExpr.get())); int count = 0; for (BSONObj obj; Runner::RUNNER_ADVANCED == runner.getNext(&obj); ) { ++count; } return count; }
TEST(MatchExpressionParserLeafTest, NotRegex1) { BSONObjBuilder b; b.appendRegex("$not", "abc", "i"); BSONObj query = BSON("x" << b.obj()); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "abc"))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "ABC"))); ASSERT(result.getValue()->matchesBSON(BSON("x" << "AC"))); }
Status CanonicalQuery::canonicalize(const string& ns, const BSONObj& query, CanonicalQuery** out) { auto_ptr<CanonicalQuery> cq(new CanonicalQuery()); LiteParsedQuery* lpq; Status parseStatus = LiteParsedQuery::make(ns, 0, 0, 0, query, &lpq); if (!parseStatus.isOK()) { return parseStatus; } cq->_pq.reset(lpq); StatusWithMatchExpression swme = MatchExpressionParser::parse(cq->_pq->getFilter()); if (!swme.isOK()) { return swme.getStatus(); } cq->_root.reset(swme.getValue()); *out = cq.release(); return Status::OK(); }
// static Status CanonicalQuery::canonicalize(const QueryMessage& qm, CanonicalQuery** out) { auto_ptr<CanonicalQuery> cq(new CanonicalQuery()); // Parse the query. LiteParsedQuery* lpq; Status parseStatus = LiteParsedQuery::make(qm, &lpq); if (!parseStatus.isOK()) { return parseStatus; } cq->_pq.reset(lpq); // Build a parse tree from the BSONObj in the parsed query. StatusWithMatchExpression swme = MatchExpressionParser::parse(cq->_pq->getFilter()); if (!swme.isOK()) { return swme.getStatus(); } cq->_root.reset(swme.getValue()); *out = cq.release(); return Status::OK(); }
int countResults(const IndexScanParams& params, BSONObj filterObj = BSONObj()) { Client::ReadContext ctx(ns()); StatusWithMatchExpression swme = MatchExpressionParser::parse(filterObj); verify(swme.isOK()); auto_ptr<MatchExpression> filterExpr(swme.getValue()); WorkingSet* ws = new WorkingSet(); PlanExecutor runner(ws, new IndexScan(params, ws, filterExpr.get())); int count = 0; for (DiskLoc dl; Runner::RUNNER_ADVANCED == runner.getNext(NULL, &dl); ) { ++count; } return count; }
// Test a tree that exceeds the maximum depth limit. TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceed) { static const int depth = 105; std::stringstream ss; for (int i = 0; i < depth / 2; i++) { ss << "{$and: [{a: 3}, {$or: [{b: 2},"; } ss << "{b: 4}"; for (int i = 0; i < depth / 2; i++) { ss << "]}]}"; } BSONObj query = fromjson(ss.str()); CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); }
// Depth limit with nested $elemMatch object. TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedElemMatch) { static const int depth = 105; std::stringstream ss; for (int i = 0; i < depth; i++) { ss << "{a: {$elemMatch: "; } ss << "{b: 5}"; for (int i = 0; i < depth; i++) { ss << "}}"; } BSONObj query = fromjson(ss.str()); CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); }
TEST(MatchExpressionParserGeo, WithinBox) { BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); }