diff options
Diffstat (limited to 'src/mongo/db/commands/plan_cache_commands_test.cpp')
-rw-r--r-- | src/mongo/db/commands/plan_cache_commands_test.cpp | 689 |
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 |