/** * Test the way cache entries are added (either "active" or "inactive") to the plan cache. */ TEST_F(QueryStageCachedPlan, QueryStageCachedPlanAddsActiveCacheEntries) { AutoGetCollectionForReadCommand ctx(&_opCtx, nss); Collection* collection = ctx.getCollection(); ASSERT(collection); // Never run - just used as a key for the cache's get() functions, since all of the other // CanonicalQueries created in this test will have this shape. const auto shapeCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 123}, b: {$gte: 123}}")); // Query can be answered by either index on "a" or index on "b". const auto noResultsCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 11}, b: {$gte: 11}}")); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kNotPresent); // Run the CachedPlanStage with a long-running child plan. Replanning should be // triggered and an inactive entry will be added. forceReplanning(collection, noResultsCq.get()); // Check for an inactive cache entry. ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentInactive); // The works should be 1 for the entry since the query we ran should not have any results. auto entry = assertGet(cache->getEntry(*shapeCq)); size_t works = 1U; ASSERT_EQ(entry->works, works); const size_t kExpectedNumWorks = 10; for (int i = 0; i < std::ceil(std::log(kExpectedNumWorks) / std::log(2)); ++i) { works *= 2; // Run another query of the same shape, which is less selective, and therefore takes // longer). auto someResultsCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 1}, b: {$gte: 0}}")); forceReplanning(collection, someResultsCq.get()); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentInactive); // The works on the cache entry should have doubled. entry = assertGet(cache->getEntry(*shapeCq)); ASSERT_EQ(entry->works, works); } // Run another query which takes less time, and be sure an active entry is created. auto fewResultsCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 6}, b: {$gte: 0}}")); forceReplanning(collection, fewResultsCq.get()); // Now there should be an active cache entry. ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentActive); entry = assertGet(cache->getEntry(*shapeCq)); // This will query will match {a: 6} through {a:9} (4 works), plus one for EOF = 5 works. ASSERT_EQ(entry->works, 5U); }
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)); }
TEST_F(QueryStageCachedPlan, DeactivatesEntriesOnReplan) { AutoGetCollectionForReadCommand ctx(&_opCtx, nss); Collection* collection = ctx.getCollection(); ASSERT(collection); // Never run - just used as a key for the cache's get() functions, since all of the other // CanonicalQueries created in this test will have this shape. const auto shapeCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 123}, b: {$gte: 123}}")); // Query can be answered by either index on "a" or index on "b". const auto noResultsCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 11}, b: {$gte: 11}}")); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kNotPresent); // Run the CachedPlanStage with a long-running child plan. Replanning should be // triggered and an inactive entry will be added. forceReplanning(collection, noResultsCq.get()); // Check for an inactive cache entry. ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentInactive); // Run the plan again, to create an active entry. forceReplanning(collection, noResultsCq.get()); // The works should be 1 for the entry since the query we ran should not have any results. ASSERT_EQ(cache->get(*noResultsCq.get()).state, PlanCache::CacheEntryState::kPresentActive); auto entry = assertGet(cache->getEntry(*shapeCq)); size_t works = 1U; ASSERT_EQ(entry->works, works); // Run another query which takes long enough to evict the active cache entry. The current // cache entry's works value is a very low number. When replanning is triggered, the cache // entry will be deactivated, but the new plan will not overwrite it, since the new plan will // have a higher works. Therefore, we will be left in an inactive entry which has had its works // value doubled from 1 to 2. auto highWorksCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 0}, b: {$gte:0}}")); forceReplanning(collection, highWorksCq.get()); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentInactive); ASSERT_EQ(assertGet(cache->getEntry(*shapeCq))->works, 2U); // Again, force replanning. This time run the initial query which finds no results. The multi // planner will choose a plan with works value lower than the existing inactive // entry. Replanning will thus deactivate the existing entry (it's already // inactive so this is a noop), then create a new entry with a works value of 1. forceReplanning(collection, noResultsCq.get()); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentActive); ASSERT_EQ(assertGet(cache->getEntry(*shapeCq))->works, 1U); }
/** * For a given query, get a runner. The runner could be a SingleSolutionRunner, a * CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc. */ Status getRunner(QueryMessage& q, Runner** out) { CanonicalQuery* rawCanonicalQuery = NULL; // Canonicalize the query and wrap it in an auto_ptr so we don't leak it if something goes // wrong. Status status = CanonicalQuery::canonicalize(q, &rawCanonicalQuery); if (!status.isOK()) { return status; } verify(rawCanonicalQuery); auto_ptr<CanonicalQuery> canonicalQuery(rawCanonicalQuery); // Try to look up a cached solution for the query. // TODO: Can the cache have negative data about a solution? PlanCache* localCache = PlanCache::get(canonicalQuery->ns()); CachedSolution* cs = localCache->get(*canonicalQuery); if (NULL != cs) { // We have a cached solution. Hand the canonical query and cached solution off to the // cached plan runner, which takes ownership of both. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*cs->solution, &root, &ws)); *out = new CachedPlanRunner(canonicalQuery.release(), cs, root, ws); return Status::OK(); } // No entry in cache for the query. We have to solve the query ourself. vector<QuerySolution*> solutions; QueryPlanner::plan(*canonicalQuery, &solutions); // We cannot figure out how to answer the query. Should this ever happen? if (0 == solutions.size()) { return Status(ErrorCodes::BadValue, "Can't create a plan for the canonical query " + canonicalQuery->toString()); } if (1 == solutions.size()) { // Only one possible plan. Run it. Build the stages from the solution. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[0], &root, &ws)); // And, run the plan. *out = new SingleSolutionRunner(canonicalQuery.release(), solutions[0], root, ws); return Status::OK(); } else { // Many solutions. Let the MultiPlanRunner pick the best, update the cache, and so on. auto_ptr<MultiPlanRunner> mpr(new MultiPlanRunner(canonicalQuery.release())); for (size_t i = 0; i < solutions.size(); ++i) { WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[i], &root, &ws)); // Takes ownership of all arguments. mpr->addPlan(solutions[i], root, ws); } *out = mpr.release(); return Status::OK(); } }
/** * 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); }
TEST_F(QueryStageCachedPlan, EntriesAreNotDeactivatedWhenInactiveEntriesDisabled) { // Set the global flag for disabling active entries. internalQueryCacheDisableInactiveEntries.store(true); ON_BLOCK_EXIT([] { internalQueryCacheDisableInactiveEntries.store(false); }); AutoGetCollectionForReadCommand ctx(&_opCtx, nss); Collection* collection = ctx.getCollection(); ASSERT(collection); // Never run - just used as a key for the cache's get() functions, since all of the other // CanonicalQueries created in this test will have this shape. const auto shapeCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 123}, b: {$gte: 123}}")); // Query can be answered by either index on "a" or index on "b". const auto noResultsCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 11}, b: {$gte: 11}}")); // We shouldn't have anything in the plan cache for this shape yet. PlanCache* cache = collection->infoCache()->getPlanCache(); ASSERT(cache); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kNotPresent); // Run the CachedPlanStage with a long-running child plan. Replanning should be // triggered and an _active_ entry will be added (since the disableInactiveEntries flag is on). forceReplanning(collection, noResultsCq.get()); // Check for an inactive cache entry. ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentActive); // Run the plan again. The entry should still be active. forceReplanning(collection, noResultsCq.get()); ASSERT_EQ(cache->get(*noResultsCq.get()).state, PlanCache::CacheEntryState::kPresentActive); // Run another query which takes long enough to evict the active cache entry. After replanning // is triggered, be sure that the the cache entry is still active. auto highWorksCq = canonicalQueryFromFilterObj(opCtx(), nss, fromjson("{a: {$gte: 0}, b: {$gte:0}}")); forceReplanning(collection, highWorksCq.get()); ASSERT_EQ(cache->get(*shapeCq).state, PlanCache::CacheEntryState::kPresentActive); }
/** * 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); }
// static Status PlanCacheListPlans::list(const PlanCache& planCache, const std::string& ns, const BSONObj& cmdObj, BSONObjBuilder* bob) { CanonicalQuery* cqRaw; Status status = canonicalize(ns, cmdObj, &cqRaw); if (!status.isOK()) { return status; } scoped_ptr<CanonicalQuery> cq(cqRaw); CachedSolution* crRaw; Status result = planCache.get(*cq, &crRaw); if (!result.isOK()) { return result; } scoped_ptr<CachedSolution> cr(crRaw); BSONArrayBuilder plansBuilder(bob->subarrayStart("plans")); size_t numPlans = cr->plannerData.size(); for (size_t i = 0; i < numPlans; ++i) { BSONObjBuilder planBob(plansBuilder.subobjStart()); // Create plan details field. // Currently, simple string representationg of // SolutionCacheData. Need to revisit format when we // need to parse user-provided plan details for planCacheAddPlan. SolutionCacheData* scd = cr->plannerData[i]; BSONObjBuilder detailsBob(planBob.subobjStart("details")); detailsBob.append("solution", scd->toString()); detailsBob.doneFast(); // XXX: Fix these field values once we have fleshed out cache entries. // reason should contain initial plan stats and score from ranking process. // feedback should contain execution stats from running the query to completion. planBob.append("reason", BSONObj()); planBob.append("feedback", BSONObj()); planBob.append("hint", scd->adminHintApplied); } plansBuilder.doneFast(); return Status::OK(); }
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); }
/** * For a given query, get a runner. The runner could be a SingleSolutionRunner, a * CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc. */ Status getRunner(CanonicalQuery* rawCanonicalQuery, Runner** out) { verify(rawCanonicalQuery); auto_ptr<CanonicalQuery> canonicalQuery(rawCanonicalQuery); // Try to look up a cached solution for the query. // TODO: Can the cache have negative data about a solution? PlanCache* localCache = PlanCache::get(canonicalQuery->ns()); if (NULL != localCache) { CachedSolution* cs = localCache->get(*canonicalQuery); if (NULL != cs) { // We have a cached solution. Hand the canonical query and cached solution off to // the cached plan runner, which takes ownership of both. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*cs->solution, &root, &ws)); *out = new CachedPlanRunner(canonicalQuery.release(), cs, root, ws); return Status::OK(); } } // No entry in cache for the query. We have to solve the query ourself. // Get the indices that we could possibly use. NamespaceDetails* nsd = nsdetails(canonicalQuery->ns().c_str()); // If this is NULL, there is no data but the query is valid. You're allowed to query for // data on an empty collection and it's not an error. There just isn't any data... if (NULL == nsd) { const std::string& ns = canonicalQuery->ns(); *out = new EOFRunner(canonicalQuery.release(), ns); return Status::OK(); } // Tailable: If the query requests tailable the collection must be capped. if (canonicalQuery->getParsed().hasOption(QueryOption_CursorTailable)) { if (!nsd->isCapped()) { return Status(ErrorCodes::BadValue, "tailable cursor requested on non capped collection"); } // If a sort is specified it must be equal to expectedSort. const BSONObj expectedSort = BSON("$natural" << 1); const BSONObj& actualSort = canonicalQuery->getParsed().getSort(); if (!actualSort.isEmpty() && !(actualSort == expectedSort)) { return Status(ErrorCodes::BadValue, "invalid sort specified for tailable cursor: " + actualSort.toString()); } } // If it's not NULL, we may have indices. vector<IndexEntry> indices; for (int i = 0; i < nsd->getCompletedIndexCount(); ++i) { auto_ptr<IndexDescriptor> desc(CatalogHack::getDescriptor(nsd, i)); indices.push_back(IndexEntry(desc->keyPattern(), desc->isMultikey(), desc->isSparse(), desc->indexName())); } vector<QuerySolution*> solutions; size_t options = QueryPlanner::DEFAULT; if (storageGlobalParams.noTableScan) { const string& ns = canonicalQuery->ns(); // There are certain cases where we ignore this restriction: bool ignore = canonicalQuery->getQueryObj().isEmpty() || (string::npos != ns.find(".system.")) || (0 == ns.find("local.")); if (!ignore) { options |= QueryPlanner::NO_TABLE_SCAN; } } else { options |= QueryPlanner::INCLUDE_COLLSCAN; } QueryPlanner::plan(*canonicalQuery, indices, options, &solutions); /* for (size_t i = 0; i < solutions.size(); ++i) { QLOG() << "solution " << i << " is " << solutions[i]->toString() << endl; } */ // We cannot figure out how to answer the query. Should this ever happen? if (0 == solutions.size()) { return Status(ErrorCodes::BadValue, "Can't create a plan for the canonical query " + canonicalQuery->toString()); } if (1 == solutions.size()) { // Only one possible plan. Run it. Build the stages from the solution. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[0], &root, &ws)); // And, run the plan. *out = new SingleSolutionRunner(canonicalQuery.release(), solutions[0], root, ws); return Status::OK(); } else { // Many solutions. Let the MultiPlanRunner pick the best, update the cache, and so on. auto_ptr<MultiPlanRunner> mpr(new MultiPlanRunner(canonicalQuery.release())); for (size_t i = 0; i < solutions.size(); ++i) { WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[i], &root, &ws)); // Takes ownership of all arguments. mpr->addPlan(solutions[i], root, ws); } *out = mpr.release(); return Status::OK(); } }
/** * For a given query, get a runner. The runner could be a SingleSolutionRunner, a * CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc. */ Status getRunner(CanonicalQuery* rawCanonicalQuery, Runner** out, size_t plannerOptions) { verify(rawCanonicalQuery); auto_ptr<CanonicalQuery> canonicalQuery(rawCanonicalQuery); // Try to look up a cached solution for the query. // TODO: Can the cache have negative data about a solution? PlanCache* localCache = PlanCache::get(canonicalQuery->ns()); if (NULL != localCache) { CachedSolution* cs = localCache->get(*canonicalQuery); if (NULL != cs) { // We have a cached solution. Hand the canonical query and cached solution off to // the cached plan runner, which takes ownership of both. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*cs->solution, &root, &ws)); *out = new CachedPlanRunner(canonicalQuery.release(), cs, root, ws); return Status::OK(); } } // No entry in cache for the query. We have to solve the query ourself. // Get the indices that we could possibly use. Database* db = cc().database(); verify( db ); Collection* collection = db->getCollection( canonicalQuery->ns() ); // This can happen as we're called by internal clients as well. if (NULL == collection) { const string& ns = canonicalQuery->ns(); *out = new EOFRunner(canonicalQuery.release(), ns); return Status::OK(); } // If we have an _id index we can use the idhack runner. if (canUseIDHack(*canonicalQuery) && collection->getIndexCatalog()->findIdIndex()) { *out = new IDHackRunner(collection, canonicalQuery.release()); return Status::OK(); } // If it's not NULL, we may have indices. Access the catalog and fill out IndexEntry(s) QueryPlannerParams plannerParams; for (int i = 0; i < collection->getIndexCatalog()->numIndexesReady(); ++i) { IndexDescriptor* desc = collection->getIndexCatalog()->getDescriptor( i ); plannerParams.indices.push_back(IndexEntry(desc->keyPattern(), desc->isMultikey(), desc->isSparse(), desc->indexName())); } // Tailable: If the query requests tailable the collection must be capped. if (canonicalQuery->getParsed().hasOption(QueryOption_CursorTailable)) { if (!collection->isCapped()) { return Status(ErrorCodes::BadValue, "tailable cursor requested on non capped collection"); } // If a sort is specified it must be equal to expectedSort. const BSONObj expectedSort = BSON("$natural" << 1); const BSONObj& actualSort = canonicalQuery->getParsed().getSort(); if (!actualSort.isEmpty() && !(actualSort == expectedSort)) { return Status(ErrorCodes::BadValue, "invalid sort specified for tailable cursor: " + actualSort.toString()); } } // Process the planning options. plannerParams.options = plannerOptions; if (storageGlobalParams.noTableScan) { const string& ns = canonicalQuery->ns(); // There are certain cases where we ignore this restriction: bool ignore = canonicalQuery->getQueryObj().isEmpty() || (string::npos != ns.find(".system.")) || (0 == ns.find("local.")); if (!ignore) { plannerParams.options |= QueryPlannerParams::NO_TABLE_SCAN; } } if (!(plannerParams.options & QueryPlannerParams::NO_TABLE_SCAN)) { plannerParams.options |= QueryPlannerParams::INCLUDE_COLLSCAN; } // If the caller wants a shard filter, make sure we're actually sharded. if (plannerParams.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { CollectionMetadataPtr collMetadata = shardingState.getCollectionMetadata(canonicalQuery->ns()); if (collMetadata) { plannerParams.shardKey = collMetadata->getKeyPattern(); } else { // If there's no metadata don't bother w/the shard filter since we won't know what // the key pattern is anyway... plannerParams.options &= ~QueryPlannerParams::INCLUDE_SHARD_FILTER; } } vector<QuerySolution*> solutions; QueryPlanner::plan(*canonicalQuery, plannerParams, &solutions); /* for (size_t i = 0; i < solutions.size(); ++i) { QLOG() << "solution " << i << " is " << solutions[i]->toString() << endl; } */ // We cannot figure out how to answer the query. Should this ever happen? if (0 == solutions.size()) { return Status(ErrorCodes::BadValue, "No query solutions"); } if (1 == solutions.size()) { // Only one possible plan. Run it. Build the stages from the solution. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[0], &root, &ws)); // And, run the plan. *out = new SingleSolutionRunner(canonicalQuery.release(), solutions[0], root, ws); return Status::OK(); } else { // Many solutions. Let the MultiPlanRunner pick the best, update the cache, and so on. auto_ptr<MultiPlanRunner> mpr(new MultiPlanRunner(canonicalQuery.release())); for (size_t i = 0; i < solutions.size(); ++i) { WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[i], &root, &ws)); // Takes ownership of all arguments. mpr->addPlan(solutions[i], root, ws); } *out = mpr.release(); return Status::OK(); } }