diff options
-rw-r--r-- | jstests/core/collation_plan_cache.js | 17 | ||||
-rw-r--r-- | jstests/core/plan_cache_list_plans.js | 170 | ||||
-rw-r--r-- | jstests/core/plan_cache_shell_helpers.js | 16 | ||||
-rw-r--r-- | src/mongo/db/commands/index_filter_commands_test.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/plan_cache_commands.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/plan_cache_commands_test.cpp | 77 | ||||
-rw-r--r-- | src/mongo/db/exec/multi_plan.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache.h | 8 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache_test.cpp | 6 | ||||
-rw-r--r-- | src/mongo/shell/collection.js | 6 | ||||
-rw-r--r-- | src/mongo/shell/query.js | 2 |
12 files changed, 216 insertions, 115 deletions
diff --git a/jstests/core/collation_plan_cache.js b/jstests/core/collation_plan_cache.js index 0eec77388e4..6cb938dbc36 100644 --- a/jstests/core/collation_plan_cache.js +++ b/jstests/core/collation_plan_cache.js @@ -72,20 +72,21 @@ coll.getPlanCache() .getPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {locale: 'en_US'}}) - .length, + .plans.length, 'unexpected number of cached plans for query'); // Test passing the query, sort, projection, and collation to getPlansByQuery() as separate // arguments. - assert.lt( - 0, - coll.getPlanCache().getPlansByQuery({a: 'foo', b: 5}, {}, {}, {locale: 'en_US'}).length, - 'unexpected number of cached plans for query'); + assert.lt(0, + coll.getPlanCache() + .getPlansByQuery({a: 'foo', b: 5}, {}, {}, {locale: 'en_US'}) + .plans.length, + 'unexpected number of cached plans for query'); // Test passing the query, sort, projection, and collation to getPlansByQuery() as separate // arguments. assert.eq(0, - coll.getPlanCache().getPlansByQuery({a: 'foo', b: 5}).length, + coll.getPlanCache().getPlansByQuery({a: 'foo', b: 5}).plans.length, 'unexpected number of cached plans for query'); // A query with a different collation should have no cached plans. @@ -94,7 +95,7 @@ coll.getPlanCache() .getPlansByQuery( {query: {a: 'foo', b: 5}, sort: {}, projection: {}, collation: {locale: 'fr_CA'}}) - .length, + .plans.length, 'unexpected number of cached plans for query'); // A query with different string locations should have no cached plans. @@ -106,7 +107,7 @@ projection: {}, collation: {locale: 'en_US'} }) - .length, + .plans.length, 'unexpected number of cached plans for query'); coll.getPlanCache().clear(); diff --git a/jstests/core/plan_cache_list_plans.js b/jstests/core/plan_cache_list_plans.js index 7ca599483ff..4da32686ae6 100644 --- a/jstests/core/plan_cache_list_plans.js +++ b/jstests/core/plan_cache_list_plans.js @@ -1,75 +1,101 @@ // Test the planCacheListPlans command. -var t = db.jstests_plan_cache_list_plans; -t.drop(); - -// Utility function to list plans for a query. -function getPlans(query, sort, projection) { - var key = {query: query, sort: sort, projection: projection}; - var res = t.runCommand('planCacheListPlans', key); - assert.commandWorked(res, 'planCacheListPlans(' + tojson(key, '', true) + ' failed'); - assert(res.hasOwnProperty('plans'), - 'plans missing from planCacheListPlans(' + tojson(key, '', true) + ') result'); - return res.plans; -} - -t.save({a: 1, b: 1}); -t.save({a: 1, b: 2}); -t.save({a: 1, b: 2}); -t.save({a: 2, b: 2}); - -// We need two indices so that the MultiPlanRunner is executed. -t.ensureIndex({a: 1}); -t.ensureIndex({a: 1, b: 1}); - -// Invalid key should be an error. -assert.eq(0, - getPlans({unknownfield: 1}, {}, {}), - 'planCacheListPlans should return empty results on unknown query shape'); - -// Create a cache entry. -assert.eq( - 1, t.find({a: 1, b: 1}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'unexpected document count'); - -// Retrieve plans for valid cache entry. -var plans = getPlans({a: 1, b: 1}, {a: -1}, {_id: 0, a: 1}); -assert.eq(2, plans.length, 'unexpected number of plans cached for query'); - -// Print every plan -// Plan details/feedback verified separately in section after Query Plan Revision tests. -print('planCacheListPlans result:'); -for (var i = 0; i < plans.length; i++) { - print('plan ' + i + ': ' + tojson(plans[i])); -} - -// -// Tests for plan reason and feedback in planCacheListPlans -// - -// Generate more plans for test query by adding indexes (compound and sparse). -// This will also clear the plan cache. -t.ensureIndex({a: -1}, {sparse: true}); -t.ensureIndex({a: 1, b: 1}); - -// Implementation note: feedback stats is calculated after 20 executions. -// See PlanCacheEntry::kMaxFeedback. -var numExecutions = 100; -for (var i = 0; i < numExecutions; i++) { - assert.eq(0, t.find({a: 3, b: 3}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'query failed'); -} - -plans = getPlans({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1}); - -// This should be obvious but feedback is available only for the first (winning) plan. -print('planCacheListPlans result (after adding indexes and completing 20 executions):'); -for (var i = 0; i < plans.length; i++) { - print('plan ' + i + ': ' + tojson(plans[i])); - assert.gt(plans[i].reason.score, 0, 'plan ' + i + ' score is invalid'); - if (i > 0) { - assert.lte(plans[i].reason.score, - plans[i - 1].reason.score, - 'plans not sorted by score in descending order. ' + - 'plan ' + i + ' has a score that is greater than that of the previous plan'); +(function() { + "use strict"; + let t = db.jstests_plan_cache_list_plans; + t.drop(); + + // Utility function to list plans for a query. + function getPlans(query, sort, projection) { + let key = {query: query, sort: sort, projection: projection}; + let res = t.runCommand('planCacheListPlans', key); + assert.commandWorked(res, 'planCacheListPlans(' + tojson(key, '', true) + ' failed'); + assert(res.hasOwnProperty('plans'), + 'plans missing from planCacheListPlans(' + tojson(key, '', true) + ') result'); + return res.plans; + } + + // Assert that timeOfCreation exists in the cache entry. The difference between the current time + // and + // the time a plan was cached should not be larger than an hour. + function checkTimeOfCreation(query, sort, projection, date) { + let key = {query: query, sort: sort, projection: projection}; + let res = t.runCommand('planCacheListPlans', key); + assert.commandWorked(res, 'planCacheListPlans(' + tojson(key, '', true) + ' failed'); + assert(res.hasOwnProperty('timeOfCreation'), + 'timeOfCreation missing from planCacheListPlans'); + let kMillisecondsPerHour = 1000 * 60 * 60; + assert.lte(Math.abs(date - res.timeOfCreation.getTime()), + kMillisecondsPerHour, + 'timeOfCreation value is incorrect'); + } + + t.save({a: 1, b: 1}); + t.save({a: 1, b: 2}); + t.save({a: 1, b: 2}); + t.save({a: 2, b: 2}); + + // We need two indices so that the MultiPlanRunner is executed. + t.ensureIndex({a: 1}); + t.ensureIndex({a: 1, b: 1}); + + // Invalid key should be an error. + assert.eq(0, + getPlans({unknownfield: 1}, {}, {}), + 'planCacheListPlans should return empty results on unknown query shape'); + + // Create a cache entry. + assert.eq(1, + t.find({a: 1, b: 1}, {_id: 0, a: 1}).sort({a: -1}).itcount(), + 'unexpected document count'); + + let now = (new Date()).getTime(); + checkTimeOfCreation({a: 1, b: 1}, {a: -1}, {_id: 0, a: 1}, now); + + // Retrieve plans for valid cache entry. + let plans = getPlans({a: 1, b: 1}, {a: -1}, {_id: 0, a: 1}); + assert.eq(2, plans.length, 'unexpected number of plans cached for query'); + + // Print every plan + // Plan details/feedback verified separately in section after Query Plan Revision tests. + print('planCacheListPlans result:'); + for (let i = 0; i < plans.length; i++) { + print('plan ' + i + ': ' + tojson(plans[i])); + } + + // + // Tests for plan reason and feedback in planCacheListPlans + // + + // Generate more plans for test query by adding indexes (compound and sparse). + // This will also clear the plan cache. + t.ensureIndex({a: -1}, {sparse: true}); + t.ensureIndex({a: 1, b: 1}); + + // Implementation note: feedback stats is calculated after 20 executions. + // See PlanCacheEntry::kMaxFeedback. + let numExecutions = 100; + for (let i = 0; i < numExecutions; i++) { + assert.eq(0, t.find({a: 3, b: 3}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'query failed'); + } + + now = (new Date()).getTime(); + checkTimeOfCreation({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1}, now); + + plans = getPlans({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1}); + + // This should be obvious but feedback is available only for the first (winning) plan. + print('planCacheListPlans result (after adding indexes and completing 20 executions):'); + for (let i = 0; i < plans.length; i++) { + print('plan ' + i + ': ' + tojson(plans[i])); + assert.gt(plans[i].reason.score, 0, 'plan ' + i + ' score is invalid'); + if (i > 0) { + assert.lte(plans[i].reason.score, + plans[i - 1].reason.score, + 'plans not sorted by score in descending order. ' + + 'plan ' + i + + ' has a score that is greater than that of the previous plan'); + } + assert(plans[i].reason.stats.hasOwnProperty('stage'), 'no stats inserted for plan ' + i); } - assert(plans[i].reason.stats.hasOwnProperty('stage'), 'no stats inserted for plan ' + i); -} +})(); diff --git a/jstests/core/plan_cache_shell_helpers.js b/jstests/core/plan_cache_shell_helpers.js index dc990b19dcc..ec4ab6df693 100644 --- a/jstests/core/plan_cache_shell_helpers.js +++ b/jstests/core/plan_cache_shell_helpers.js @@ -82,7 +82,7 @@ assert.eq(getShapes(), // should return empty array on non-existent query shape. assert.eq(0, - planCache.getPlansByQuery({unknownfield: 1}).length, + planCache.getPlansByQuery({unknownfield: 1}).plans.length, 'collection.getPlanCache().getPlansByQuery() should return empty results ' + 'on non-existent collection'); // should error on missing required field query. @@ -92,16 +92,16 @@ assert.throws(function() { // Invoke with various permutations of required (query) and optional (projection, sort) arguments. assert.eq(getPlans(queryB, sortC, projectionB), - planCache.getPlansByQuery(queryB, projectionB, sortC), + planCache.getPlansByQuery(queryB, projectionB, sortC).plans, 'plans from collection.getPlanCache().getPlansByQuery() different from command result'); assert.eq(getPlans(queryB, {}, projectionB), - planCache.getPlansByQuery(queryB, projectionB), + planCache.getPlansByQuery(queryB, projectionB).plans, 'plans from collection.getPlanCache().getPlansByQuery() different from command result'); assert.eq(getPlans(queryB, sortC, {}), - planCache.getPlansByQuery(queryB, undefined, sortC), + planCache.getPlansByQuery(queryB, undefined, sortC).plans, 'plans from collection.getPlanCache().getPlansByQuery() different from command result'); assert.eq(getPlans(queryB, {}, {}), - planCache.getPlansByQuery(queryB), + planCache.getPlansByQuery(queryB).plans, 'plans from collection.getPlanCache().getPlansByQuery() different from command result'); // getPlansByQuery() will also accept a single argument with the query shape object @@ -114,7 +114,7 @@ assert.eq(getPlans(queryB, {}, {}), // } var shapeB = {query: queryB, projection: projectionB, sort: sortC}; assert.eq(getPlans(queryB, sortC, projectionB), - planCache.getPlansByQuery(shapeB), + planCache.getPlansByQuery(shapeB).plans, 'collection.getPlanCache().getPlansByQuery() did not accept query shape object'); // Should return empty array on missing or extra fields in query shape object. @@ -122,14 +122,14 @@ assert.eq(getPlans(queryB, sortC, projectionB), // as the 'query' component which will result in the server returning an empty // array of plans. assert.eq(0, - planCache.getPlansByQuery({query: queryB}).length, + planCache.getPlansByQuery({query: queryB}).plans.length, 'collection.getPlanCache.getPlansByQuery should return empty results on ' + 'incomplete query shape'); assert.eq( 0, planCache .getPlansByQuery({query: queryB, sort: sortC, projection: projectionB, unknown_field: 1}) - .length, + .plans.length, 'collection.getPlanCache.getPlansByQuery should return empty results on ' + 'invalid query shape'); diff --git a/src/mongo/db/commands/index_filter_commands_test.cpp b/src/mongo/db/commands/index_filter_commands_test.cpp index 17dec1cb003..da0b0b919d0 100644 --- a/src/mongo/db/commands/index_filter_commands_test.cpp +++ b/src/mongo/db/commands/index_filter_commands_test.cpp @@ -140,7 +140,10 @@ void addQueryShapeToPlanCache(OperationContext* opCtx, qs.cacheData->tree.reset(new PlanCacheIndexTree()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - ASSERT_OK(planCache->add(*cq, solns, createDecision(1U))); + ASSERT_OK(planCache->add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); } /** diff --git a/src/mongo/db/commands/plan_cache_commands.cpp b/src/mongo/db/commands/plan_cache_commands.cpp index 3704105d17f..002a4e9d94d 100644 --- a/src/mongo/db/commands/plan_cache_commands.cpp +++ b/src/mongo/db/commands/plan_cache_commands.cpp @@ -406,10 +406,8 @@ Status PlanCacheListPlans::list(OperationContext* opCtx, 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. + // Create the plan details field. Currently, this is a simple string representation of + // SolutionCacheData. SolutionCacheData* scd = entry->plannerData[i]; BSONObjBuilder detailsBob(planBob.subobjStart("details")); detailsBob.append("solution", scd->toString()); @@ -442,8 +440,12 @@ Status PlanCacheListPlans::list(OperationContext* opCtx, planBob.append("filterSet", scd->indexFilterApplied); } + plansBuilder.doneFast(); + // Append the time the entry was inserted into the plan cache. + bob->append("timeOfCreation", entry->timeOfCreation); + return Status::OK(); } diff --git a/src/mongo/db/commands/plan_cache_commands_test.cpp b/src/mongo/db/commands/plan_cache_commands_test.cpp index b93b9da43e2..fb0ed6c3e03 100644 --- a/src/mongo/db/commands/plan_cache_commands_test.cpp +++ b/src/mongo/db/commands/plan_cache_commands_test.cpp @@ -151,7 +151,10 @@ TEST(PlanCacheCommandsTest, planCacheListQueryShapesOneKey) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); vector<BSONObj> shapes = getShapes(planCache); ASSERT_EQUALS(shapes.size(), 1U); @@ -183,7 +186,10 @@ TEST(PlanCacheCommandsTest, planCacheClearAllShapes) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); ASSERT_EQUALS(getShapes(planCache).size(), 1U); // Clear cache and confirm number of keys afterwards. @@ -339,8 +345,14 @@ TEST(PlanCacheCommandsTest, planCacheClearOneKey) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cqA, solns, createDecision(1U)).transitional_ignore(); - planCache.add(*cqB, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cqA, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); + ASSERT_OK(planCache.add(*cqB, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); // Check keys in cache before dropping {b: 1} vector<BSONObj> shapesBefore = getShapes(planCache); @@ -396,8 +408,14 @@ TEST(PlanCacheCommandsTest, planCacheClearOneKeyCollation) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(1U)).transitional_ignore(); - planCache.add(*cqCollation, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); + ASSERT_OK(planCache.add(*cqCollation, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); // Check keys in cache before dropping the query with collation. vector<BSONObj> shapesBefore = getShapes(planCache); @@ -539,7 +557,10 @@ TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionTrue) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); vector<BSONObj> plans = getPlans(planCache, cq->getQueryObj(), @@ -568,7 +589,10 @@ TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionFalse) { std::vector<QuerySolution*> solns; solns.push_back(&qs); solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(2U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(2U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); vector<BSONObj> plans = getPlans(planCache, cq->getQueryObj(), @@ -605,11 +629,17 @@ TEST(PlanCacheCommandsTest, planCacheListPlansCollation) { qs.cacheData.reset(createSolutionCacheData()); std::vector<QuerySolution*> solns; solns.push_back(&qs); - planCache.add(*cq, solns, createDecision(1U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cq, + solns, + createDecision(1U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); std::vector<QuerySolution*> twoSolns; twoSolns.push_back(&qs); twoSolns.push_back(&qs); - planCache.add(*cqCollation, twoSolns, createDecision(2U)).transitional_ignore(); + ASSERT_OK(planCache.add(*cqCollation, + twoSolns, + createDecision(2U), + opCtx->getServiceContext()->getPreciseClockSource()->now())); // Normal query should have one solution. vector<BSONObj> plans = getPlans(planCache, @@ -628,4 +658,31 @@ TEST(PlanCacheCommandsTest, planCacheListPlansCollation) { ASSERT_EQUALS(plansCollation.size(), 2U); } +TEST(PlanCacheCommandsTest, planCacheListPlansTimeOfCreationIsCorrect) { + QueryTestServiceContext serviceContext; + auto opCtx = serviceContext.makeOperationContext(); + + // Create a canonical query. + auto qr = stdx::make_unique<QueryRequest>(nss); + qr->setFilter(fromjson("{a: 1}")); + auto statusWithCQ = CanonicalQuery::canonicalize(opCtx.get(), std::move(qr)); + ASSERT_OK(statusWithCQ.getStatus()); + auto cq = std::move(statusWithCQ.getValue()); + + // Plan cache with one entry. + PlanCache planCache; + QuerySolution qs; + qs.cacheData.reset(createSolutionCacheData()); + std::vector<QuerySolution*> solns; + solns.push_back(&qs); + auto now = opCtx->getServiceContext()->getPreciseClockSource()->now(); + ASSERT_OK(planCache.add(*cq, solns, createDecision(1U), now)); + + PlanCacheEntry* out; + ASSERT_OK(planCache.getEntry(*cq, &out)); + unique_ptr<PlanCacheEntry> entry(out); + + ASSERT_EQ(entry->timeOfCreation, now); +} + } // namespace diff --git a/src/mongo/db/exec/multi_plan.cpp b/src/mongo/db/exec/multi_plan.cpp index 4b32b06ade4..d4c9e732717 100644 --- a/src/mongo/db/exec/multi_plan.cpp +++ b/src/mongo/db/exec/multi_plan.cpp @@ -324,7 +324,10 @@ Status MultiPlanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) { if (validSolutions) { _collection->infoCache() ->getPlanCache() - ->add(*_query, solutions, ranking.release()) + ->add(*_query, + solutions, + ranking.release(), + getOpCtx()->getServiceContext()->getPreciseClockSource()->now()) .transitional_ignore(); } } diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp index 43502d96c0b..577cacd3a2b 100644 --- a/src/mongo/db/query/plan_cache.cpp +++ b/src/mongo/db/query/plan_cache.cpp @@ -433,6 +433,7 @@ PlanCacheEntry* PlanCacheEntry::clone() const { entry->sort = sort.getOwned(); entry->projection = projection.getOwned(); entry->collation = collation.getOwned(); + entry->timeOfCreation = timeOfCreation; // Copy performance stats. for (size_t i = 0; i < feedback.size(); ++i) { @@ -448,7 +449,8 @@ std::string PlanCacheEntry::toString() const { return str::stream() << "(query: " << query.toString() << ";sort: " << sort.toString() << ";projection: " << projection.toString() << ";collation: " << collation.toString() - << ";solutions: " << plannerData.size() << ")"; + << ";solutions: " << plannerData.size() + << ";timeOfCreation: " << timeOfCreation.toString() << ")"; } std::string CachedSolution::toString() const { @@ -694,7 +696,8 @@ void PlanCache::encodeKeyForProj(const BSONObj& projObj, StringBuilder* keyBuild Status PlanCache::add(const CanonicalQuery& query, const std::vector<QuerySolution*>& solns, - PlanRankingDecision* why) { + PlanRankingDecision* why, + Date_t now) { invariant(why); if (solns.empty()) { @@ -721,6 +724,8 @@ Status PlanCache::add(const CanonicalQuery& query, if (query.getCollator()) { entry->collation = query.getCollator()->getSpec().toBSON(); } + entry->timeOfCreation = now; + // Strip projections on $-prefixed fields, as these are added by internal callers of the query // system and are not considered part of the user projection. diff --git a/src/mongo/db/query/plan_cache.h b/src/mongo/db/query/plan_cache.h index c532a514640..e97e57db06b 100644 --- a/src/mongo/db/query/plan_cache.h +++ b/src/mongo/db/query/plan_cache.h @@ -267,6 +267,7 @@ public: BSONObj sort; BSONObj projection; BSONObj collation; + Date_t timeOfCreation; // // Performance stats @@ -312,7 +313,9 @@ public: * Record solutions for query. Best plan is first element in list. * Each query in the cache will have more than 1 plan because we only * add queries which are considered by the multi plan runner (which happens - * only when the query planner generates multiple candidate plans). + * only when the query planner generates multiple candidate plans). Callers are responsible + * for passing the current time so that the time the plan cache entry was created is stored + * in the plan cache. * * Takes ownership of 'why'. * @@ -321,7 +324,8 @@ public: */ Status add(const CanonicalQuery& query, const std::vector<QuerySolution*>& solns, - PlanRankingDecision* why); + PlanRankingDecision* why, + Date_t now); /** * Look up the cached data access for the provided 'query'. Used by the query planner diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp index eb588910785..19115ccc1cb 100644 --- a/src/mongo/db/query/plan_cache_test.cpp +++ b/src/mongo/db/query/plan_cache_test.cpp @@ -427,7 +427,8 @@ TEST(PlanCacheTest, AddEmptySolutions) { unique_ptr<CanonicalQuery> cq(canonicalize("{a: 1}")); std::vector<QuerySolution*> solns; unique_ptr<PlanRankingDecision> decision(createDecision(1U)); - ASSERT_NOT_OK(planCache.add(*cq, solns, decision.get())); + QueryTestServiceContext serviceContext; + ASSERT_NOT_OK(planCache.add(*cq, solns, decision.get(), Date_t{})); } TEST(PlanCacheTest, AddValidSolution) { @@ -441,7 +442,8 @@ TEST(PlanCacheTest, AddValidSolution) { // Check if key is in cache before and after add(). ASSERT_FALSE(planCache.contains(*cq)); - ASSERT_OK(planCache.add(*cq, solns, createDecision(1U))); + QueryTestServiceContext serviceContext; + ASSERT_OK(planCache.add(*cq, solns, createDecision(1U), Date_t{})); ASSERT_TRUE(planCache.contains(*cq)); ASSERT_EQUALS(planCache.size(), 1U); diff --git a/src/mongo/shell/collection.js b/src/mongo/shell/collection.js index 998d4db3e90..8d242a347a9 100644 --- a/src/mongo/shell/collection.js +++ b/src/mongo/shell/collection.js @@ -1780,10 +1780,8 @@ PlanCache.prototype.clear = function() { * List plans for a query shape. */ PlanCache.prototype.getPlansByQuery = function(query, projection, sort, collation) { - return this - ._runCommandThrowOnError("planCacheListPlans", - this._parseQueryShape(query, projection, sort, collation)) - .plans; + return this._runCommandThrowOnError("planCacheListPlans", + this._parseQueryShape(query, projection, sort, collation)); }; /** diff --git a/src/mongo/shell/query.js b/src/mongo/shell/query.js index 7b1cf7be1b5..6de7edb3661 100644 --- a/src/mongo/shell/query.js +++ b/src/mongo/shell/query.js @@ -905,7 +905,7 @@ QueryPlan.prototype.help = function() { * List plans for a query shape. */ QueryPlan.prototype.getPlans = function() { - return this._cursor._collection.getPlanCache().getPlansByQuery(this._cursor); + return this._cursor._collection.getPlanCache().getPlansByQuery(this._cursor).plans; }; /** |