void run() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("a" << "2d" << "b" << 1)); addIndex(BSON("a" << "2d")); BSONObj query = fromjson( "{$or: [{a: {$geoWithin: {$centerSphere: [[0,0],10]}}}," "{a: {$geoWithin: {$centerSphere: [[1,1],10]}}}]}"); 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()); 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)); }
void run() { OldClientWriteContext ctx(&_txn, ns()); addIndex(BSON("a" << "2d" << "b" << 1)); addIndex(BSON("a" << "2d")); BSONObj query = fromjson("{$or: [{a: {$geoWithin: {$centerSphere: [[0,0],10]}}}," "{a: {$geoWithin: {$centerSphere: [[1,1],10]}}}]}"); CanonicalQuery* rawCq; ASSERT_OK(CanonicalQuery::canonicalize(ns(), query, &rawCq)); boost::scoped_ptr<CanonicalQuery> cq(rawCq); Collection* collection = ctx.getCollection(); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; boost::scoped_ptr<SubplanStage> subplan(new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); // Plan selection should succeed due to falling back on regular planning. PlanYieldPolicy yieldPolicy(NULL, PlanExecutor::YIELD_MANUAL); ASSERT_OK(subplan->pickBestPlan(&yieldPolicy)); }
void run() { AutoGetCollectionForRead ctx(&_txn, nss.ns()); Collection* collection = ctx.getCollection(); ASSERT(collection); // Query can be answered by either index on "a" or index on "b". auto statusWithCQ = CanonicalQuery::canonicalize(nss, fromjson("{a: {$gte: 8}, b: 1}")); ASSERT_OK(statusWithCQ.getStatus()); const std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); CachedSolution* rawCachedSolution; ASSERT_NOT_OK(cache->get(*cq, &rawCachedSolution)); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); // Queued data stage will return a failure during the cached plan trial period. auto mockChild = stdx::make_unique<QueuedDataStage>(&_txn, &_ws); mockChild->pushBack(PlanStage::FAILURE); // High enough so that we shouldn't trigger a replan based on works. const size_t decisionWorks = 50; CachedPlanStage cachedPlanStage( &_txn, collection, &_ws, cq.get(), plannerParams, decisionWorks, mockChild.release()); // This should succeed after triggering a replan. PlanYieldPolicy yieldPolicy(nullptr, PlanExecutor::YIELD_MANUAL); ASSERT_OK(cachedPlanStage.pickBestPlan(&yieldPolicy)); // Make sure that we get 2 legit results back. size_t numResults = 0; PlanStage::StageState state = PlanStage::NEED_TIME; while (state != PlanStage::IS_EOF) { WorkingSetID id = WorkingSet::INVALID_ID; state = cachedPlanStage.work(&id); ASSERT_NE(state, PlanStage::FAILURE); ASSERT_NE(state, PlanStage::DEAD); if (state == PlanStage::ADVANCED) { WorkingSetMember* member = _ws.get(id); ASSERT(cq->root()->matchesBSON(member->obj.value())); numResults++; } } ASSERT_EQ(numResults, 2U); // Plan cache should still be empty, as we don't write to it when we replan a failed // query. ASSERT_NOT_OK(cache->get(*cq, &rawCachedSolution)); }
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)); }
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)); }
void run() { OldClientWriteContext ctx(&_txn, nss.ns()); addIndex(BSON("a" << 1 << "b" << 1)); addIndex(BSON("a" << 1)); addIndex(BSON("c" << 1)); for (int i = 0; i < 10; i++) { insert(BSON("a" << 1 << "b" << i << "c" << i)); } // Running this query should not create any cache entries. For the first branch, it's // because there are no matching results. For the second branch it's because there is only // one relevant index. BSONObj query = fromjson("{$or: [{a: 1, b: 15}, {c: 1}]}"); Collection* collection = ctx.getCollection(); auto statusWithCQ = CanonicalQuery::canonicalize(nss, query); 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(nullptr, PlanExecutor::YIELD_MANUAL); 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)); }
void run() { OldClientWriteContext ctx(&_txn, ns()); addIndex(BSON("a" << 1 << "b" << 1)); addIndex(BSON("a" << 1 << "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 branch. The second // branch should tie, meaning that nothing is inserted into the plan cache. BSONObj query = fromjson("{$or: [{a: 1, b: 3}, {a: 1}]}"); Collection* collection = ctx.getCollection(); CanonicalQuery* rawCq; ASSERT_OK(CanonicalQuery::canonicalize(ns(), query, &rawCq)); boost::scoped_ptr<CanonicalQuery> cq(rawCq); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); WorkingSet ws; boost::scoped_ptr<SubplanStage> subplan(new SubplanStage(&_txn, collection, &ws, plannerParams, cq.get())); PlanYieldPolicy yieldPolicy(NULL, PlanExecutor::YIELD_MANUAL); 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, then the first branch should come from the cache, // but the second is re-planned due to tying on the first run. 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)); }
/** * Test that hitting the cached plan stage trial period's threshold for work cycles causes the * query to be replanned. Also verify that the replanning results in a new plan cache entry. */ TEST_F(QueryStageCachedPlan, QueryStageCachedPlanHitMaxWorks) { AutoGetCollectionForReadCommand ctx(&_opCtx, nss); Collection* collection = ctx.getCollection(); ASSERT(collection); // Query can be answered by either index on "a" or index on "b". auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(fromjson("{a: {$gte: 8}, b: 1}")); auto statusWithCQ = CanonicalQuery::canonicalize(opCtx(), std::move(qr)); ASSERT_OK(statusWithCQ.getStatus()); const std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); ASSERT_EQ(cache->get(*cq).state, PlanCache::CacheEntryState::kNotPresent); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_opCtx, collection, cq.get(), &plannerParams); // Set up queued data stage to take a long time before returning EOF. Should be long // enough to trigger a replan. const size_t decisionWorks = 10; const size_t mockWorks = 1U + static_cast<size_t>(internalQueryCacheEvictionRatio * decisionWorks); auto mockChild = stdx::make_unique<QueuedDataStage>(&_opCtx, &_ws); for (size_t i = 0; i < mockWorks; i++) { mockChild->pushBack(PlanStage::NEED_TIME); } CachedPlanStage cachedPlanStage( &_opCtx, collection, &_ws, cq.get(), plannerParams, decisionWorks, mockChild.release()); // This should succeed after triggering a replan. PlanYieldPolicy yieldPolicy(PlanExecutor::NO_YIELD, _opCtx.getServiceContext()->getFastClockSource()); ASSERT_OK(cachedPlanStage.pickBestPlan(&yieldPolicy)); ASSERT_EQ(getNumResultsForStage(_ws, &cachedPlanStage, cq.get()), 2U); // This time we expect to find something in the plan cache. Replans after hitting the // works threshold result in a cache entry. ASSERT_EQ(cache->get(*cq).state, PlanCache::CacheEntryState::kPresentInactive); }
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); }
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)); BSONObj query = fromjson("{$or: [{a: 1}, {a: {$ne:1}}]}"); BSONObj sort = BSON("d" << 1); BSONObj projection; auto cq = unittest::assertGet(CanonicalQuery::canonicalize(nss, query, sort, projection)); 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(nullptr, PlanExecutor::YIELD_MANUAL); 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); }
/** * Test that on failure, the cached plan stage replans the query but does not create a new cache * entry. */ TEST_F(QueryStageCachedPlan, QueryStageCachedPlanFailure) { AutoGetCollectionForReadCommand ctx(&_opCtx, nss); Collection* collection = ctx.getCollection(); ASSERT(collection); // Query can be answered by either index on "a" or index on "b". auto qr = stdx::make_unique<QueryRequest>(nss); qr->setFilter(fromjson("{a: {$gte: 8}, b: 1}")); auto statusWithCQ = CanonicalQuery::canonicalize(opCtx(), std::move(qr)); ASSERT_OK(statusWithCQ.getStatus()); const std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); ASSERT_EQ(cache->get(*cq).state, PlanCache::CacheEntryState::kNotPresent); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_opCtx, collection, cq.get(), &plannerParams); // Queued data stage will return a failure during the cached plan trial period. auto mockChild = stdx::make_unique<QueuedDataStage>(&_opCtx, &_ws); mockChild->pushBack(PlanStage::FAILURE); // High enough so that we shouldn't trigger a replan based on works. const size_t decisionWorks = 50; CachedPlanStage cachedPlanStage( &_opCtx, collection, &_ws, cq.get(), plannerParams, decisionWorks, mockChild.release()); // This should succeed after triggering a replan. PlanYieldPolicy yieldPolicy(PlanExecutor::NO_YIELD, _opCtx.getServiceContext()->getFastClockSource()); ASSERT_OK(cachedPlanStage.pickBestPlan(&yieldPolicy)); ASSERT_EQ(getNumResultsForStage(_ws, &cachedPlanStage, cq.get()), 2U); // Plan cache should still be empty, as we don't write to it when we replan a failed // query. ASSERT_EQ(cache->get(*cq).state, PlanCache::CacheEntryState::kNotPresent); }
void forceReplanning(Collection* collection, CanonicalQuery* cq) { // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_opCtx, collection, cq, &plannerParams); const size_t decisionWorks = 10; const size_t mockWorks = 1U + static_cast<size_t>(internalQueryCacheEvictionRatio * decisionWorks); auto mockChild = stdx::make_unique<QueuedDataStage>(&_opCtx, &_ws); for (size_t i = 0; i < mockWorks; i++) { mockChild->pushBack(PlanStage::NEED_TIME); } CachedPlanStage cachedPlanStage( &_opCtx, collection, &_ws, cq, plannerParams, decisionWorks, mockChild.release()); // This should succeed after triggering a replan. PlanYieldPolicy yieldPolicy(PlanExecutor::NO_YIELD, _opCtx.getServiceContext()->getFastClockSource()); ASSERT_OK(cachedPlanStage.pickBestPlan(&yieldPolicy)); }
void run() { AutoGetCollectionForRead ctx(&_txn, nss.ns()); Collection* collection = ctx.getCollection(); ASSERT(collection); // Query can be answered by either index on "a" or index on "b". auto statusWithCQ = CanonicalQuery::canonicalize(nss, fromjson("{a: {$gte: 8}, b: 1}")); ASSERT_OK(statusWithCQ.getStatus()); const std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); CachedSolution* rawCachedSolution; ASSERT_NOT_OK(cache->get(*cq, &rawCachedSolution)); // Get planner params. QueryPlannerParams plannerParams; fillOutPlannerParams(&_txn, collection, cq.get(), &plannerParams); // Set up queued data stage to take a long time before returning EOF. Should be long // enough to trigger a replan. const size_t decisionWorks = 10; const size_t mockWorks = 1U + static_cast<size_t>(internalQueryCacheEvictionRatio * decisionWorks); auto mockChild = stdx::make_unique<QueuedDataStage>(&_txn, &_ws); for (size_t i = 0; i < mockWorks; i++) { mockChild->pushBack(PlanStage::NEED_TIME); } CachedPlanStage cachedPlanStage( &_txn, collection, &_ws, cq.get(), plannerParams, decisionWorks, mockChild.release()); // This should succeed after triggering a replan. PlanYieldPolicy yieldPolicy(nullptr, PlanExecutor::YIELD_MANUAL); ASSERT_OK(cachedPlanStage.pickBestPlan(&yieldPolicy)); // Make sure that we get 2 legit results back. size_t numResults = 0; PlanStage::StageState state = PlanStage::NEED_TIME; while (state != PlanStage::IS_EOF) { WorkingSetID id = WorkingSet::INVALID_ID; state = cachedPlanStage.work(&id); ASSERT_NE(state, PlanStage::FAILURE); ASSERT_NE(state, PlanStage::DEAD); if (state == PlanStage::ADVANCED) { WorkingSetMember* member = _ws.get(id); ASSERT(cq->root()->matchesBSON(member->obj.value())); numResults++; } } ASSERT_EQ(numResults, 2U); // This time we expect to find something in the plan cache. Replans after hitting the // works threshold result in a cache entry. ASSERT_OK(cache->get(*cq, &rawCachedSolution)); const std::unique_ptr<CachedSolution> cachedSolution(rawCachedSolution); }