TEST(MatchExpressionParserGeoNear, ParseInvalidGeoNear) { { BSONObj query = fromjson("{loc: {$maxDistance: 100, $geoNear: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$minDistance: 100, $geoNear: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$geoNear: [0,0], $eq: 1}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } { BSONObj query = fromjson("{loc: {$geoNear: [0,0], $maxDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } { BSONObj query = fromjson("{loc: {$geoNear: [0,0], $minDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator) .status_with_transitional_ignore(), UserException); } }
void run() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("b" << 1 << "a" << 1)); addIndex(BSON("c" << 1 << "a" << 1)); BSONObj query = fromjson("{a: 1, $or: [{b: 2}, {c: 3}]}"); // Two of these documents match. insert(BSON("_id" << 1 << "a" << 1 << "b" << 2)); insert(BSON("_id" << 2 << "a" << 2 << "b" << 2)); insert(BSON("_id" << 3 << "a" << 1 << "c" << 3)); insert(BSON("_id" << 4 << "a" << 1 << "c" << 4)); auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(query); auto cq = unittest::assertGet(CanonicalQuery::canonicalize( txn(), std::move(qr), ExtensionsCallbackDisallowExtensions())); Collection* collection = ctx.getCollection(); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; std::unique_ptr<SubplanStage> subplan( new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); // Plan selection should succeed due to falling back on regular planning. PlanYieldPolicy yieldPolicy(PlanExecutor::YIELD_MANUAL, _clock); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); // Work the stage until it produces all results. size_t numResults = 0; PlanStage::StageState stageState = PlanStage::NEED_TIME; while (stageState != PlanStage::IS_EOF) { WorkingSetID id = WorkingSet::INVALID_ID; stageState = subplan->work(&id); ASSERT_NE(stageState, PlanStage::DEAD); ASSERT_NE(stageState, PlanStage::FAILURE); if (stageState == PlanStage::ADVANCED) { ++numResults; WorkingSetMember* member = ws.get(id); ASSERT(member->hasObj()); ASSERT(member->obj.value() == BSON("_id" << 1 << "a" << 1 << "b" << 2) || member->obj.value() == BSON("_id" << 3 << "a" << 1 << "c" << 3)); } } ASSERT_EQ(numResults, 2U); }
void run() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("a" << 1 << "b" << 1)); addIndex(BSON("a" << 1 << "c" << 1)); addIndex(BSON("d" << 1)); for (int i = 0; i < 10; i++) { insert(BSON("a" << 1 << "e" << 1 << "d" << 1)); } // Running this query should not create any cache entries. For the first branch, it's // because plans using the {a: 1, b: 1} and {a: 1, c: 1} indices should tie during plan // ranking. For the second branch it's because there is only one relevant index. BSONObj query = fromjson("{$or: [{a: 1, e: 1}, {d: 1}]}"); Collection* collection = ctx.getCollection(); auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(query); auto statusWithCQ = CanonicalQuery::canonicalize( txn(), std::move(qr), ExtensionsCallbackDisallowExtensions()); ASSERT_OK(statusWithCQ.getStatus()); std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; std::unique_ptr<SubplanStage> subplan( new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); PlanYieldPolicy yieldPolicy(PlanExecutor::YIELD_MANUAL, _clock); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); // Nothing is in the cache yet, so neither branch should have been planned from // the plan cache. ASSERT_FALSE(subplan->branchPlannedFromCache(0)); ASSERT_FALSE(subplan->branchPlannedFromCache(1)); // If we run the query again, it should again be the case that neither branch gets planned // from the cache (because the first call to pickBestPlan() refrained from creating any // cache entries). ws.clear(); subplan.reset(new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); ASSERT_FALSE(subplan->branchPlannedFromCache(0)); ASSERT_FALSE(subplan->branchPlannedFromCache(1)); }
TEST(MatchExpressionParserGeo, WithinBox) { BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions()); 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(MatchExpressionParserTreeTest, OREmbedded) { BSONObj query1 = BSON("$or" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); BSONObj query2 = BSON("$or" << BSON_ARRAY(query1)); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query2, 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() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("a" << 1)); addIndex(BSON("a" << 1 << "b" << 1)); addIndex(BSON("c" << 1)); for (int i = 0; i < 10; i++) { insert(BSON("a" << 1 << "b" << i << "c" << i)); } // This query should result in a plan cache entry for the first $or branch, because // there are two competing indices. The second branch has only one relevant index, so // its winning plan should not be cached. BSONObj query = fromjson("{$or: [{a: 1, b: 3}, {c: 1}]}"); Collection* collection = ctx.getCollection(); auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(query); auto statusWithCQ = CanonicalQuery::canonicalize( txn(), std::move(qr), ExtensionsCallbackDisallowExtensions()); ASSERT_OK(statusWithCQ.getStatus()); std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; std::unique_ptr<SubplanStage> subplan( new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); PlanYieldPolicy yieldPolicy(PlanExecutor::YIELD_MANUAL, _clock); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); // Nothing is in the cache yet, so neither branch should have been planned from // the plan cache. ASSERT_FALSE(subplan->branchPlannedFromCache(0)); ASSERT_FALSE(subplan->branchPlannedFromCache(1)); // If we repeat the same query, the plan for the first branch should have come from // the cache. ws.clear(); subplan.reset(new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); ASSERT_TRUE(subplan->branchPlannedFromCache(0)); ASSERT_FALSE(subplan->branchPlannedFromCache(1)); }
// 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, ExtensionsCallbackDisallowExtensions()); 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(MatchExpressionParserTreeTest, AND1) { BSONObj query = BSON("$and" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); 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))); ASSERT(result.getValue()->matchesBSON(BSON("x" << 1 << "y" << 2))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2 << "y" << 2))); }
TEST(MatchExpressionParserGeoNear, ParseNear) { BSONObj query = fromjson( "{loc:{$near:{$maxDistance:100, " "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions()); 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(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"))); }
// 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 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()); }
void run() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("a" << 1 << "b" << 1)); addIndex(BSON("a" << 1 << "c" << 1)); // Every doc matches. insert(BSON("_id" << 1 << "a" << 1)); insert(BSON("_id" << 2 << "a" << 2)); insert(BSON("_id" << 3 << "a" << 3)); insert(BSON("_id" << 4)); auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(fromjson("{$or: [{a: 1}, {a: {$ne:1}}]}")); qr->setSort(BSON("d" << 1)); auto cq = unittest::assertGet(CanonicalQuery::canonicalize( txn(), std::move(qr), ExtensionsCallbackDisallowExtensions())); Collection* collection = ctx.getCollection(); QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; std::unique_ptr<SubplanStage> subplan( new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); PlanYieldPolicy yieldPolicy(PlanExecutor::YIELD_MANUAL, _clock); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); size_t numResults = 0; PlanStage::StageState stageState = PlanStage::NEED_TIME; while (stageState != PlanStage::IS_EOF) { WorkingSetID id = WorkingSet::INVALID_ID; stageState = subplan->work(&id); ASSERT_NE(stageState, PlanStage::DEAD); ASSERT_NE(stageState, PlanStage::FAILURE); if (stageState == PlanStage::ADVANCED) { ++numResults; } } ASSERT_EQ(numResults, 4U); }
Status AuthzManagerExternalStateMock::_queryVector( const NamespaceString& collectionName, const BSONObj& query, std::vector<BSONObjCollection::iterator>* result) { CollatorInterface* collator = nullptr; StatusWithMatchExpression parseResult = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); if (!parseResult.isOK()) { return parseResult.getStatus(); } const std::unique_ptr<MatchExpression> matcher = std::move(parseResult.getValue()); NamespaceDocumentMap::iterator mapIt = _documents.find(collectionName); if (mapIt == _documents.end()) return Status::OK(); for (BSONObjCollection::iterator vecIt = mapIt->second.begin(); vecIt != mapIt->second.end(); ++vecIt) { if (matcher->matchesBSON(*vecIt)) { result->push_back(vecIt); } } return Status::OK(); }
TEST(MatchExpressionParserGeoNear, ParseInvalidNear) { { BSONObj query = fromjson("{loc: {$maxDistance: 100, $near: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$minDistance: 100, $near: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$near: [0,0], $maxDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator), UserException); } { BSONObj query = fromjson("{loc: {$near: [0,0], $minDistance: {}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator), UserException); } { BSONObj query = fromjson("{loc: {$near: [0,0], $eq: 40}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator), UserException); } { BSONObj query = fromjson("{loc: {$eq: 40, $near: [0,0]}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson( "{loc: {$near: [0,0], $geoWithin: {$geometry: {type: \"Polygon\", coordinates: []}}}}"); const CollatorInterface* collator = nullptr; ASSERT_THROWS( MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator), UserException); } { BSONObj query = fromjson("{loc: {$near: {$foo: 1}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$minDistance: 10}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression result = MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_FALSE(result.isOK()); } }