summaryrefslogtreecommitdiff
path: root/src/mongo/db/commands/plan_cache_commands_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/commands/plan_cache_commands_test.cpp')
-rw-r--r--src/mongo/db/commands/plan_cache_commands_test.cpp689
1 files changed, 346 insertions, 343 deletions
diff --git a/src/mongo/db/commands/plan_cache_commands_test.cpp b/src/mongo/db/commands/plan_cache_commands_test.cpp
index 86eecdbda7e..8a7eee783d8 100644
--- a/src/mongo/db/commands/plan_cache_commands_test.cpp
+++ b/src/mongo/db/commands/plan_cache_commands_test.cpp
@@ -45,371 +45,374 @@ using namespace mongo;
namespace {
- using std::string;
- using std::unique_ptr;
- using std::vector;
-
- static const char* ns = "test.t";
-
- /**
- * Tests for planCacheListQueryShapes
- */
-
- /**
- * Utility function to get list of keys in the cache.
- */
- std::vector<BSONObj> getShapes(const PlanCache& planCache) {
- BSONObjBuilder bob;
- ASSERT_OK(PlanCacheListQueryShapes::list(planCache, &bob));
- BSONObj resultObj = bob.obj();
- BSONElement shapesElt = resultObj.getField("shapes");
- ASSERT_EQUALS(shapesElt.type(), mongo::Array);
- vector<BSONElement> shapesEltArray = shapesElt.Array();
- vector<BSONObj> shapes;
- for (vector<BSONElement>::const_iterator i = shapesEltArray.begin();
- i != shapesEltArray.end(); ++i) {
- const BSONElement& elt = *i;
-
- ASSERT_TRUE(elt.isABSONObj());
- BSONObj obj = elt.Obj();
-
- // Check required fields.
- // query
- BSONElement queryElt = obj.getField("query");
- ASSERT_TRUE(queryElt.isABSONObj());
-
- // sort
- BSONElement sortElt = obj.getField("sort");
- ASSERT_TRUE(sortElt.isABSONObj());
-
- // projection
- BSONElement projectionElt = obj.getField("projection");
- ASSERT_TRUE(projectionElt.isABSONObj());
-
- // All fields OK. Append to vector.
- shapes.push_back(obj.getOwned());
- }
- return shapes;
- }
+using std::string;
+using std::unique_ptr;
+using std::vector;
- /**
- * Utility function to create a SolutionCacheData
- */
- SolutionCacheData* createSolutionCacheData() {
- unique_ptr<SolutionCacheData> scd(new SolutionCacheData());
- scd->tree.reset(new PlanCacheIndexTree());
- return scd.release();
- }
+static const char* ns = "test.t";
- /**
- * Utility function to create a PlanRankingDecision
- */
- PlanRankingDecision* createDecision(size_t numPlans) {
- unique_ptr<PlanRankingDecision> why(new PlanRankingDecision());
- for (size_t i = 0; i < numPlans; ++i) {
- CommonStats common("COLLSCAN");
- unique_ptr<PlanStageStats> stats(new PlanStageStats(common, STAGE_COLLSCAN));
- stats->specific.reset(new CollectionScanStats());
- why->stats.mutableVector().push_back(stats.release());
- why->scores.push_back(0U);
- why->candidateOrder.push_back(i);
- }
- return why.release();
- }
+/**
+ * Tests for planCacheListQueryShapes
+ */
- TEST(PlanCacheCommandsTest, planCacheListQueryShapesEmpty) {
- PlanCache empty;
- vector<BSONObj> shapes = getShapes(empty);
- ASSERT_TRUE(shapes.empty());
- }
+/**
+ * Utility function to get list of keys in the cache.
+ */
+std::vector<BSONObj> getShapes(const PlanCache& planCache) {
+ BSONObjBuilder bob;
+ ASSERT_OK(PlanCacheListQueryShapes::list(planCache, &bob));
+ BSONObj resultObj = bob.obj();
+ BSONElement shapesElt = resultObj.getField("shapes");
+ ASSERT_EQUALS(shapesElt.type(), mongo::Array);
+ vector<BSONElement> shapesEltArray = shapesElt.Array();
+ vector<BSONObj> shapes;
+ for (vector<BSONElement>::const_iterator i = shapesEltArray.begin(); i != shapesEltArray.end();
+ ++i) {
+ const BSONElement& elt = *i;
- TEST(PlanCacheCommandsTest, planCacheListQueryShapesOneKey) {
- // Create a canonical query
- CanonicalQuery* cqRaw;
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cq(cqRaw);
-
- // Plan cache with one entry
- PlanCache planCache;
- QuerySolution qs;
- qs.cacheData.reset(createSolutionCacheData());
- std::vector<QuerySolution*> solns;
- solns.push_back(&qs);
- planCache.add(*cq, solns, createDecision(1U));
-
- vector<BSONObj> shapes = getShapes(planCache);
- ASSERT_EQUALS(shapes.size(), 1U);
- ASSERT_EQUALS(shapes[0].getObjectField("query"), cq->getQueryObj());
- ASSERT_EQUALS(shapes[0].getObjectField("sort"), cq->getParsed().getSort());
- ASSERT_EQUALS(shapes[0].getObjectField("projection"), cq->getParsed().getProj());
- }
+ ASSERT_TRUE(elt.isABSONObj());
+ BSONObj obj = elt.Obj();
- /**
- * Tests for planCacheClear
- */
-
- TEST(PlanCacheCommandsTest, planCacheClearAllShapes) {
- // Create a canonical query
- CanonicalQuery* cqRaw;
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cq(cqRaw);
-
- // Plan cache with one entry
- PlanCache planCache;
- QuerySolution qs;
- OperationContextNoop txn;
-
- qs.cacheData.reset(createSolutionCacheData());
- std::vector<QuerySolution*> solns;
- solns.push_back(&qs);
- planCache.add(*cq, solns, createDecision(1U));
- ASSERT_EQUALS(getShapes(planCache).size(), 1U);
-
- // Clear cache and confirm number of keys afterwards.
- ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, BSONObj()));
- ASSERT_EQUALS(getShapes(planCache).size(), 0U);
- }
+ // Check required fields.
+ // query
+ BSONElement queryElt = obj.getField("query");
+ ASSERT_TRUE(queryElt.isABSONObj());
- /**
- * Tests for PlanCacheCommand::makeCacheKey
- * Mostly validation on the input parameters
- */
-
- TEST(PlanCacheCommandsTest, Canonicalize) {
- // Invalid parameters
- PlanCache planCache;
- CanonicalQuery* cqRaw;
- OperationContextNoop txn;
-
- // Missing query field
- ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{}"), &cqRaw));
- // Query needs to be an object
- ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: 1}"), &cqRaw));
- // Sort needs to be an object
- ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {}, sort: 1}"),
- &cqRaw));
- // Bad query (invalid sort order)
- ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {}, sort: {a: 0}}"),
- &cqRaw));
-
- // Valid parameters
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {a: 1, b: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> query(cqRaw);
-
-
- // Equivalent query should generate same key.
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {b: 1, a: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> equivQuery(cqRaw);
- ASSERT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*equivQuery));
-
- // Sort query should generate different key from unsorted query.
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns,
- fromjson("{query: {a: 1, b: 1}, sort: {a: 1, b: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> sortQuery1(cqRaw);
- ASSERT_NOT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*sortQuery1));
-
- // Confirm sort arguments are properly delimited (SERVER-17158)
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns,
- fromjson("{query: {a: 1, b: 1}, sort: {aab: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> sortQuery2(cqRaw);
- ASSERT_NOT_EQUALS(planCache.computeKey(*sortQuery1), planCache.computeKey(*sortQuery2));
-
- // Changing order and/or value of predicates should not change key
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns,
- fromjson("{query: {b: 3, a: 3}, sort: {a: 1, b: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> sortQuery3(cqRaw);
- ASSERT_EQUALS(planCache.computeKey(*sortQuery1), planCache.computeKey(*sortQuery3));
-
- // Projected query should generate different key from unprojected query.
- ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns,
- fromjson("{query: {a: 1, b: 1}, projection: {_id: 0, a: 1}}"), &cqRaw));
- unique_ptr<CanonicalQuery> projectionQuery(cqRaw);
- ASSERT_NOT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*projectionQuery));
- }
+ // sort
+ BSONElement sortElt = obj.getField("sort");
+ ASSERT_TRUE(sortElt.isABSONObj());
+
+ // projection
+ BSONElement projectionElt = obj.getField("projection");
+ ASSERT_TRUE(projectionElt.isABSONObj());
- /**
- * Tests for planCacheClear (single query shape)
- */
-
- TEST(PlanCacheCommandsTest, planCacheClearInvalidParameter) {
- PlanCache planCache;
- OperationContextNoop txn;
-
- // Query field type must be BSON object.
- ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: 12345}")));
- ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: /keyisnotregex/}")));
- // Query must pass canonicalization.
- ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns,
- fromjson("{query: {a: {$no_such_op: 1}}}")));
- // Sort present without query is an error.
- ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{sort: {a: 1}}")));
- // Projection present without query is an error.
- ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns,
- fromjson("{projection: {_id: 0, a: 1}}")));
+ // All fields OK. Append to vector.
+ shapes.push_back(obj.getOwned());
}
+ return shapes;
+}
- TEST(PlanCacheCommandsTest, planCacheClearUnknownKey) {
- PlanCache planCache;
- OperationContextNoop txn;
+/**
+ * Utility function to create a SolutionCacheData
+ */
+SolutionCacheData* createSolutionCacheData() {
+ unique_ptr<SolutionCacheData> scd(new SolutionCacheData());
+ scd->tree.reset(new PlanCacheIndexTree());
+ return scd.release();
+}
- ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: {a: 1}}")));
+/**
+ * Utility function to create a PlanRankingDecision
+ */
+PlanRankingDecision* createDecision(size_t numPlans) {
+ unique_ptr<PlanRankingDecision> why(new PlanRankingDecision());
+ for (size_t i = 0; i < numPlans; ++i) {
+ CommonStats common("COLLSCAN");
+ unique_ptr<PlanStageStats> stats(new PlanStageStats(common, STAGE_COLLSCAN));
+ stats->specific.reset(new CollectionScanStats());
+ why->stats.mutableVector().push_back(stats.release());
+ why->scores.push_back(0U);
+ why->candidateOrder.push_back(i);
}
+ return why.release();
+}
+
+TEST(PlanCacheCommandsTest, planCacheListQueryShapesEmpty) {
+ PlanCache empty;
+ vector<BSONObj> shapes = getShapes(empty);
+ ASSERT_TRUE(shapes.empty());
+}
+
+TEST(PlanCacheCommandsTest, planCacheListQueryShapesOneKey) {
+ // Create a canonical query
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Plan cache with one entry
+ PlanCache planCache;
+ QuerySolution qs;
+ qs.cacheData.reset(createSolutionCacheData());
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ planCache.add(*cq, solns, createDecision(1U));
+
+ vector<BSONObj> shapes = getShapes(planCache);
+ ASSERT_EQUALS(shapes.size(), 1U);
+ ASSERT_EQUALS(shapes[0].getObjectField("query"), cq->getQueryObj());
+ ASSERT_EQUALS(shapes[0].getObjectField("sort"), cq->getParsed().getSort());
+ ASSERT_EQUALS(shapes[0].getObjectField("projection"), cq->getParsed().getProj());
+}
- TEST(PlanCacheCommandsTest, planCacheClearOneKey) {
- // Create 2 canonical queries.
- CanonicalQuery* cqRaw;
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cqA(cqRaw);
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{b: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cqB(cqRaw);
-
- // Create plan cache with 2 entries.
- PlanCache planCache;
- QuerySolution qs;
- qs.cacheData.reset(createSolutionCacheData());
- std::vector<QuerySolution*> solns;
- solns.push_back(&qs);
- planCache.add(*cqA, solns, createDecision(1U));
- planCache.add(*cqB, solns, createDecision(1U));
-
- // Check keys in cache before dropping {b: 1}
- vector<BSONObj> shapesBefore = getShapes(planCache);
- ASSERT_EQUALS(shapesBefore.size(), 2U);
- BSONObj shapeA = BSON("query" << cqA->getQueryObj() << "sort" << cqA->getParsed().getSort()
- << "projection" << cqA->getParsed().getProj());
- BSONObj shapeB = BSON("query" << cqB->getQueryObj() << "sort" << cqB->getParsed().getSort()
- << "projection" << cqB->getParsed().getProj());
- ASSERT_TRUE(std::find(shapesBefore.begin(), shapesBefore.end(), shapeA) != shapesBefore.end());
- ASSERT_TRUE(std::find(shapesBefore.begin(), shapesBefore.end(), shapeB) != shapesBefore.end());
-
- // Drop {b: 1} from cache. Make sure {a: 1} is still in cache afterwards.
- BSONObjBuilder bob;
- OperationContextNoop txn;
-
- ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, BSON("query" << cqB->getQueryObj())));
- vector<BSONObj> shapesAfter = getShapes(planCache);
- ASSERT_EQUALS(shapesAfter.size(), 1U);
- ASSERT_EQUALS(shapesAfter[0], shapeA);
- }
+/**
+ * Tests for planCacheClear
+ */
- /**
- * Tests for planCacheListPlans
- */
-
- /**
- * Function to extract plan ID from BSON element.
- * Validates planID during extraction.
- * Each BSON element contains an embedded BSON object with the following layout:
- * {
- * plan: <plan_id>,
- * details: <plan_details>,
- * reason: <ranking_stats>,
- * feedback: <execution_stats>,
- * source: <source>
- * }
- * Compilation note: GCC 4.4 has issues with getPlan() declared as a function object.
- */
- BSONObj getPlan(const BSONElement& elt) {
- ASSERT_TRUE(elt.isABSONObj());
- BSONObj obj = elt.Obj();
+TEST(PlanCacheCommandsTest, planCacheClearAllShapes) {
+ // Create a canonical query
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cq(cqRaw);
- // Check required fields.
- // details
- BSONElement detailsElt = obj.getField("details");
- ASSERT_TRUE(detailsElt.isABSONObj());
+ // Plan cache with one entry
+ PlanCache planCache;
+ QuerySolution qs;
+ OperationContextNoop txn;
- // reason
- BSONElement reasonElt = obj.getField("reason");
- ASSERT_TRUE(reasonElt.isABSONObj());
+ qs.cacheData.reset(createSolutionCacheData());
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ planCache.add(*cq, solns, createDecision(1U));
+ ASSERT_EQUALS(getShapes(planCache).size(), 1U);
- // feedback
- BSONElement feedbackElt = obj.getField("feedback");
- ASSERT_TRUE(feedbackElt.isABSONObj());
+ // Clear cache and confirm number of keys afterwards.
+ ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, BSONObj()));
+ ASSERT_EQUALS(getShapes(planCache).size(), 0U);
+}
- return obj.getOwned();
- }
+/**
+ * Tests for PlanCacheCommand::makeCacheKey
+ * Mostly validation on the input parameters
+ */
- /**
- * Utility function to get list of plan IDs for a query in the cache.
- */
- vector<BSONObj> getPlans(const PlanCache& planCache, const BSONObj& query,
- const BSONObj& sort, const BSONObj& projection) {
- OperationContextNoop txn;
-
- BSONObjBuilder bob;
- BSONObj cmdObj = BSON("query" << query << "sort" << sort << "projection" << projection);
- ASSERT_OK(PlanCacheListPlans::list(&txn, planCache, ns, cmdObj, &bob));
- BSONObj resultObj = bob.obj();
- BSONElement plansElt = resultObj.getField("plans");
- ASSERT_EQUALS(plansElt.type(), mongo::Array);
- vector<BSONElement> planEltArray = plansElt.Array();
- ASSERT_FALSE(planEltArray.empty());
- vector<BSONObj> plans(planEltArray.size());
- std::transform(planEltArray.begin(), planEltArray.end(), plans.begin(), getPlan);
- return plans;
- }
+TEST(PlanCacheCommandsTest, Canonicalize) {
+ // Invalid parameters
+ PlanCache planCache;
+ CanonicalQuery* cqRaw;
+ OperationContextNoop txn;
+
+ // Missing query field
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{}"), &cqRaw));
+ // Query needs to be an object
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: 1}"), &cqRaw));
+ // Sort needs to be an object
+ ASSERT_NOT_OK(
+ PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {}, sort: 1}"), &cqRaw));
+ // Bad query (invalid sort order)
+ ASSERT_NOT_OK(
+ PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {}, sort: {a: 0}}"), &cqRaw));
+
+ // Valid parameters
+ ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {a: 1, b: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> query(cqRaw);
+
+
+ // Equivalent query should generate same key.
+ ASSERT_OK(PlanCacheCommand::canonicalize(&txn, ns, fromjson("{query: {b: 1, a: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> equivQuery(cqRaw);
+ ASSERT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*equivQuery));
+
+ // Sort query should generate different key from unsorted query.
+ ASSERT_OK(PlanCacheCommand::canonicalize(
+ &txn, ns, fromjson("{query: {a: 1, b: 1}, sort: {a: 1, b: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> sortQuery1(cqRaw);
+ ASSERT_NOT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*sortQuery1));
+
+ // Confirm sort arguments are properly delimited (SERVER-17158)
+ ASSERT_OK(PlanCacheCommand::canonicalize(
+ &txn, ns, fromjson("{query: {a: 1, b: 1}, sort: {aab: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> sortQuery2(cqRaw);
+ ASSERT_NOT_EQUALS(planCache.computeKey(*sortQuery1), planCache.computeKey(*sortQuery2));
+
+ // Changing order and/or value of predicates should not change key
+ ASSERT_OK(PlanCacheCommand::canonicalize(
+ &txn, ns, fromjson("{query: {b: 3, a: 3}, sort: {a: 1, b: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> sortQuery3(cqRaw);
+ ASSERT_EQUALS(planCache.computeKey(*sortQuery1), planCache.computeKey(*sortQuery3));
+
+ // Projected query should generate different key from unprojected query.
+ ASSERT_OK(PlanCacheCommand::canonicalize(
+ &txn, ns, fromjson("{query: {a: 1, b: 1}, projection: {_id: 0, a: 1}}"), &cqRaw));
+ unique_ptr<CanonicalQuery> projectionQuery(cqRaw);
+ ASSERT_NOT_EQUALS(planCache.computeKey(*query), planCache.computeKey(*projectionQuery));
+}
- TEST(PlanCacheCommandsTest, planCacheListPlansInvalidParameter) {
- PlanCache planCache;
- BSONObjBuilder ignored;
- OperationContextNoop txn;
-
- // Missing query field is not ok.
- ASSERT_NOT_OK(PlanCacheListPlans::list(&txn, planCache, ns, BSONObj(), &ignored));
- // Query field type must be BSON object.
- ASSERT_NOT_OK(PlanCacheListPlans::list(&txn, planCache, ns, fromjson("{query: 12345}"),
- &ignored));
- ASSERT_NOT_OK(PlanCacheListPlans::list(&txn, planCache, ns, fromjson("{query: /keyisnotregex/}"),
- &ignored));
- }
+/**
+ * Tests for planCacheClear (single query shape)
+ */
- TEST(PlanCacheCommandsTest, planCacheListPlansUnknownKey) {
- // Leave the plan cache empty.
- PlanCache planCache;
- OperationContextNoop txn;
+TEST(PlanCacheCommandsTest, planCacheClearInvalidParameter) {
+ PlanCache planCache;
+ OperationContextNoop txn;
+
+ // Query field type must be BSON object.
+ ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: 12345}")));
+ ASSERT_NOT_OK(
+ PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: /keyisnotregex/}")));
+ // Query must pass canonicalization.
+ ASSERT_NOT_OK(
+ PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: {a: {$no_such_op: 1}}}")));
+ // Sort present without query is an error.
+ ASSERT_NOT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{sort: {a: 1}}")));
+ // Projection present without query is an error.
+ ASSERT_NOT_OK(
+ PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{projection: {_id: 0, a: 1}}")));
+}
+
+TEST(PlanCacheCommandsTest, planCacheClearUnknownKey) {
+ PlanCache planCache;
+ OperationContextNoop txn;
+
+ ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, fromjson("{query: {a: 1}}")));
+}
+
+TEST(PlanCacheCommandsTest, planCacheClearOneKey) {
+ // Create 2 canonical queries.
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cqA(cqRaw);
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{b: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cqB(cqRaw);
+
+ // Create plan cache with 2 entries.
+ PlanCache planCache;
+ QuerySolution qs;
+ qs.cacheData.reset(createSolutionCacheData());
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ planCache.add(*cqA, solns, createDecision(1U));
+ planCache.add(*cqB, solns, createDecision(1U));
+
+ // Check keys in cache before dropping {b: 1}
+ vector<BSONObj> shapesBefore = getShapes(planCache);
+ ASSERT_EQUALS(shapesBefore.size(), 2U);
+ BSONObj shapeA = BSON("query" << cqA->getQueryObj() << "sort" << cqA->getParsed().getSort()
+ << "projection" << cqA->getParsed().getProj());
+ BSONObj shapeB = BSON("query" << cqB->getQueryObj() << "sort" << cqB->getParsed().getSort()
+ << "projection" << cqB->getParsed().getProj());
+ ASSERT_TRUE(std::find(shapesBefore.begin(), shapesBefore.end(), shapeA) != shapesBefore.end());
+ ASSERT_TRUE(std::find(shapesBefore.begin(), shapesBefore.end(), shapeB) != shapesBefore.end());
+
+ // Drop {b: 1} from cache. Make sure {a: 1} is still in cache afterwards.
+ BSONObjBuilder bob;
+ OperationContextNoop txn;
+
+ ASSERT_OK(PlanCacheClear::clear(&txn, &planCache, ns, BSON("query" << cqB->getQueryObj())));
+ vector<BSONObj> shapesAfter = getShapes(planCache);
+ ASSERT_EQUALS(shapesAfter.size(), 1U);
+ ASSERT_EQUALS(shapesAfter[0], shapeA);
+}
- BSONObjBuilder ignored;
- ASSERT_OK(PlanCacheListPlans::list(&txn, planCache, ns, fromjson("{query: {a: 1}}"), &ignored));
- }
+/**
+ * Tests for planCacheListPlans
+ */
- TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionTrue) {
- // Create a canonical query
- CanonicalQuery* cqRaw;
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cq(cqRaw);
-
- // Plan cache with one entry
- PlanCache planCache;
- QuerySolution qs;
- qs.cacheData.reset(createSolutionCacheData());
- std::vector<QuerySolution*> solns;
- solns.push_back(&qs);
- planCache.add(*cq, solns, createDecision(1U));
-
- vector<BSONObj> plans = getPlans(planCache, cq->getQueryObj(),
- cq->getParsed().getSort(), cq->getParsed().getProj());
- ASSERT_EQUALS(plans.size(), 1U);
- }
+/**
+ * Function to extract plan ID from BSON element.
+ * Validates planID during extraction.
+ * Each BSON element contains an embedded BSON object with the following layout:
+ * {
+ * plan: <plan_id>,
+ * details: <plan_details>,
+ * reason: <ranking_stats>,
+ * feedback: <execution_stats>,
+ * source: <source>
+ * }
+ * Compilation note: GCC 4.4 has issues with getPlan() declared as a function object.
+ */
+BSONObj getPlan(const BSONElement& elt) {
+ ASSERT_TRUE(elt.isABSONObj());
+ BSONObj obj = elt.Obj();
- TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionFalse) {
- // Create a canonical query
- CanonicalQuery* cqRaw;
- ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
- unique_ptr<CanonicalQuery> cq(cqRaw);
-
- // Plan cache with one entry
- PlanCache planCache;
- QuerySolution qs;
- qs.cacheData.reset(createSolutionCacheData());
- // Add cache entry with 2 solutions.
- std::vector<QuerySolution*> solns;
- solns.push_back(&qs);
- solns.push_back(&qs);
- planCache.add(*cq, solns, createDecision(2U));
-
- vector<BSONObj> plans = getPlans(planCache, cq->getQueryObj(),
- cq->getParsed().getSort(), cq->getParsed().getProj());
- ASSERT_EQUALS(plans.size(), 2U);
- }
+ // Check required fields.
+ // details
+ BSONElement detailsElt = obj.getField("details");
+ ASSERT_TRUE(detailsElt.isABSONObj());
+
+ // reason
+ BSONElement reasonElt = obj.getField("reason");
+ ASSERT_TRUE(reasonElt.isABSONObj());
+
+ // feedback
+ BSONElement feedbackElt = obj.getField("feedback");
+ ASSERT_TRUE(feedbackElt.isABSONObj());
+
+ return obj.getOwned();
+}
+
+/**
+ * Utility function to get list of plan IDs for a query in the cache.
+ */
+vector<BSONObj> getPlans(const PlanCache& planCache,
+ const BSONObj& query,
+ const BSONObj& sort,
+ const BSONObj& projection) {
+ OperationContextNoop txn;
+
+ BSONObjBuilder bob;
+ BSONObj cmdObj = BSON("query" << query << "sort" << sort << "projection" << projection);
+ ASSERT_OK(PlanCacheListPlans::list(&txn, planCache, ns, cmdObj, &bob));
+ BSONObj resultObj = bob.obj();
+ BSONElement plansElt = resultObj.getField("plans");
+ ASSERT_EQUALS(plansElt.type(), mongo::Array);
+ vector<BSONElement> planEltArray = plansElt.Array();
+ ASSERT_FALSE(planEltArray.empty());
+ vector<BSONObj> plans(planEltArray.size());
+ std::transform(planEltArray.begin(), planEltArray.end(), plans.begin(), getPlan);
+ return plans;
+}
+
+TEST(PlanCacheCommandsTest, planCacheListPlansInvalidParameter) {
+ PlanCache planCache;
+ BSONObjBuilder ignored;
+ OperationContextNoop txn;
+
+ // Missing query field is not ok.
+ ASSERT_NOT_OK(PlanCacheListPlans::list(&txn, planCache, ns, BSONObj(), &ignored));
+ // Query field type must be BSON object.
+ ASSERT_NOT_OK(
+ PlanCacheListPlans::list(&txn, planCache, ns, fromjson("{query: 12345}"), &ignored));
+ ASSERT_NOT_OK(PlanCacheListPlans::list(
+ &txn, planCache, ns, fromjson("{query: /keyisnotregex/}"), &ignored));
+}
+
+TEST(PlanCacheCommandsTest, planCacheListPlansUnknownKey) {
+ // Leave the plan cache empty.
+ PlanCache planCache;
+ OperationContextNoop txn;
+
+ BSONObjBuilder ignored;
+ ASSERT_OK(PlanCacheListPlans::list(&txn, planCache, ns, fromjson("{query: {a: 1}}"), &ignored));
+}
+
+TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionTrue) {
+ // Create a canonical query
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Plan cache with one entry
+ PlanCache planCache;
+ QuerySolution qs;
+ qs.cacheData.reset(createSolutionCacheData());
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ planCache.add(*cq, solns, createDecision(1U));
+
+ vector<BSONObj> plans = getPlans(
+ planCache, cq->getQueryObj(), cq->getParsed().getSort(), cq->getParsed().getProj());
+ ASSERT_EQUALS(plans.size(), 1U);
+}
+
+TEST(PlanCacheCommandsTest, planCacheListPlansOnlyOneSolutionFalse) {
+ // Create a canonical query
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
+ unique_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Plan cache with one entry
+ PlanCache planCache;
+ QuerySolution qs;
+ qs.cacheData.reset(createSolutionCacheData());
+ // Add cache entry with 2 solutions.
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ solns.push_back(&qs);
+ planCache.add(*cq, solns, createDecision(2U));
+
+ vector<BSONObj> plans = getPlans(
+ planCache, cq->getQueryObj(), cq->getParsed().getSort(), cq->getParsed().getProj());
+ ASSERT_EQUALS(plans.size(), 2U);
+}
} // namespace