summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/collation_plan_cache.js17
-rw-r--r--jstests/core/plan_cache_list_plans.js170
-rw-r--r--jstests/core/plan_cache_shell_helpers.js16
-rw-r--r--src/mongo/db/commands/index_filter_commands_test.cpp5
-rw-r--r--src/mongo/db/commands/plan_cache_commands.cpp10
-rw-r--r--src/mongo/db/commands/plan_cache_commands_test.cpp77
-rw-r--r--src/mongo/db/exec/multi_plan.cpp5
-rw-r--r--src/mongo/db/query/plan_cache.cpp9
-rw-r--r--src/mongo/db/query/plan_cache.h8
-rw-r--r--src/mongo/db/query/plan_cache_test.cpp6
-rw-r--r--src/mongo/shell/collection.js6
-rw-r--r--src/mongo/shell/query.js2
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;
};
/**