/**
* Copyright (C) 2013 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
#include "mongo/platform/basic.h"
#include
#include
#include "mongo/base/init.h"
#include "mongo/base/status.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database.h"
#include "mongo/db/client.h"
#include "mongo/db/commands/plan_cache_commands.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/query/explain.h"
#include "mongo/db/query/plan_ranker.h"
#include "mongo/util/hex.h"
#include "mongo/util/log.h"
namespace {
using std::string;
using std::unique_ptr;
using namespace mongo;
/**
* Retrieves a collection's plan cache from the database.
*/
static Status getPlanCache(OperationContext* opCtx,
Collection* collection,
const string& ns,
PlanCache** planCacheOut) {
*planCacheOut = NULL;
if (NULL == collection) {
return Status(ErrorCodes::BadValue, "no such collection");
}
CollectionInfoCache* infoCache = collection->infoCache();
invariant(infoCache);
PlanCache* planCache = infoCache->getPlanCache();
invariant(planCache);
*planCacheOut = planCache;
return Status::OK();
}
//
// Command instances.
// Registers commands with the command system and make commands
// available to the client.
//
MONGO_INITIALIZER_WITH_PREREQUISITES(SetupPlanCacheCommands, MONGO_NO_PREREQUISITES)
(InitializerContext* context) {
// PlanCacheCommand constructors refer to static ActionType instances.
// Registering commands in a mongo static initializer ensures that
// the ActionType construction will be completed first.
new PlanCacheListQueryShapes();
new PlanCacheClear();
new PlanCacheListPlans();
return Status::OK();
}
} // namespace
namespace mongo {
using std::string;
using std::stringstream;
using std::vector;
using std::unique_ptr;
PlanCacheCommand::PlanCacheCommand(const string& name,
const string& helpText,
ActionType actionType)
: BasicCommand(name), helpText(helpText), actionType(actionType) {}
bool PlanCacheCommand::run(OperationContext* opCtx,
const string& dbname,
const BSONObj& cmdObj,
BSONObjBuilder& result) {
const NamespaceString nss(CommandHelpers::parseNsCollectionRequired(dbname, cmdObj));
Status status = runPlanCacheCommand(opCtx, nss.ns(), cmdObj, &result);
uassertStatusOK(status);
return true;
}
bool PlanCacheCommand::supportsWriteConcern(const BSONObj& cmd) const {
return false;
}
Command::AllowedOnSecondary PlanCacheCommand::secondaryAllowed(ServiceContext*) const {
return AllowedOnSecondary::kOptIn;
}
std::string PlanCacheCommand::help() const {
return helpText;
}
Status PlanCacheCommand::checkAuthForCommand(Client* client,
const std::string& dbname,
const BSONObj& cmdObj) const {
AuthorizationSession* authzSession = AuthorizationSession::get(client);
ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
if (authzSession->isAuthorizedForActionsOnResource(pattern, actionType)) {
return Status::OK();
}
return Status(ErrorCodes::Unauthorized, "unauthorized");
}
// static
StatusWith> PlanCacheCommand::canonicalize(OperationContext* opCtx,
const string& ns,
const BSONObj& cmdObj) {
// query - required
BSONElement queryElt = cmdObj.getField("query");
if (queryElt.eoo()) {
return Status(ErrorCodes::BadValue, "required field query missing");
}
if (!queryElt.isABSONObj()) {
return Status(ErrorCodes::BadValue, "required field query must be an object");
}
if (queryElt.eoo()) {
return Status(ErrorCodes::BadValue, "required field query missing");
}
BSONObj queryObj = queryElt.Obj();
// sort - optional
BSONElement sortElt = cmdObj.getField("sort");
BSONObj sortObj;
if (!sortElt.eoo()) {
if (!sortElt.isABSONObj()) {
return Status(ErrorCodes::BadValue, "optional field sort must be an object");
}
sortObj = sortElt.Obj();
}
// projection - optional
BSONElement projElt = cmdObj.getField("projection");
BSONObj projObj;
if (!projElt.eoo()) {
if (!projElt.isABSONObj()) {
return Status(ErrorCodes::BadValue, "optional field projection must be an object");
}
projObj = projElt.Obj();
}
// collation - optional
BSONObj collationObj;
if (auto collationElt = cmdObj["collation"]) {
if (!collationElt.isABSONObj()) {
return Status(ErrorCodes::BadValue, "optional field collation must be an object");
}
collationObj = collationElt.Obj();
if (collationObj.isEmpty()) {
return Status(ErrorCodes::BadValue,
"optional field collation cannot be an empty object");
}
}
// Create canonical query
const NamespaceString nss(ns);
auto qr = stdx::make_unique(std::move(nss));
qr->setFilter(queryObj);
qr->setSort(sortObj);
qr->setProj(projObj);
qr->setCollation(collationObj);
const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
const boost::intrusive_ptr expCtx;
auto statusWithCQ =
CanonicalQuery::canonicalize(opCtx,
std::move(qr),
expCtx,
extensionsCallback,
MatchExpressionParser::kAllowAllSpecialFeatures);
if (!statusWithCQ.isOK()) {
return statusWithCQ.getStatus();
}
return std::move(statusWithCQ.getValue());
}
PlanCacheListQueryShapes::PlanCacheListQueryShapes()
: PlanCacheCommand("planCacheListQueryShapes",
"Displays all query shapes in a collection.",
ActionType::planCacheRead) {}
Status PlanCacheListQueryShapes::runPlanCacheCommand(OperationContext* opCtx,
const string& ns,
const BSONObj& cmdObj,
BSONObjBuilder* bob) {
// This is a read lock. The query cache is owned by the collection.
AutoGetCollectionForReadCommand ctx(opCtx, NamespaceString(ns));
PlanCache* planCache;
Status status = getPlanCache(opCtx, ctx.getCollection(), ns, &planCache);
if (!status.isOK()) {
// No collection - return results with empty shapes array.
BSONArrayBuilder arrayBuilder(bob->subarrayStart("shapes"));
arrayBuilder.doneFast();
return Status::OK();
}
return list(*planCache, bob);
}
// static
Status PlanCacheListQueryShapes::list(const PlanCache& planCache, BSONObjBuilder* bob) {
invariant(bob);
// Fetch all cached solutions from plan cache.
auto entries = planCache.getAllEntries();
BSONArrayBuilder arrayBuilder(bob->subarrayStart("shapes"));
for (auto&& entry : entries) {
invariant(entry);
BSONObjBuilder shapeBuilder(arrayBuilder.subobjStart());
shapeBuilder.append("query", entry->query);
shapeBuilder.append("sort", entry->sort);
shapeBuilder.append("projection", entry->projection);
if (!entry->collation.isEmpty()) {
shapeBuilder.append("collation", entry->collation);
}
shapeBuilder.append("queryHash", unsignedIntToFixedLengthHex(entry->queryHash));
shapeBuilder.doneFast();
}
arrayBuilder.doneFast();
return Status::OK();
}
PlanCacheClear::PlanCacheClear()
: PlanCacheCommand("planCacheClear",
"Drops one or all cached queries in a collection.",
ActionType::planCacheWrite) {}
Status PlanCacheClear::runPlanCacheCommand(OperationContext* opCtx,
const std::string& ns,
const BSONObj& cmdObj,
BSONObjBuilder* bob) {
// This is a read lock. The query cache is owned by the collection.
AutoGetCollectionForReadCommand ctx(opCtx, NamespaceString(ns));
PlanCache* planCache;
Status status = getPlanCache(opCtx, ctx.getCollection(), ns, &planCache);
if (!status.isOK()) {
// No collection - nothing to do. Return OK status.
return Status::OK();
}
return clear(opCtx, planCache, ns, cmdObj);
}
// static
Status PlanCacheClear::clear(OperationContext* opCtx,
PlanCache* planCache,
const string& ns,
const BSONObj& cmdObj) {
invariant(planCache);
// According to the specification, the planCacheClear command runs in two modes:
// - clear all query shapes; or
// - clear plans for single query shape when a query shape is described in the
// command arguments.
if (cmdObj.hasField("query")) {
auto statusWithCQ = PlanCacheCommand::canonicalize(opCtx, ns, cmdObj);
if (!statusWithCQ.isOK()) {
return statusWithCQ.getStatus();
}
unique_ptr cq = std::move(statusWithCQ.getValue());
Status result = planCache->remove(*cq);
if (!result.isOK()) {
invariant(result.code() == ErrorCodes::NoSuchKey);
LOG(1) << ns << ": query shape doesn't exist in PlanCache - "
<< redact(cq->getQueryObj()) << "(sort: " << cq->getQueryRequest().getSort()
<< "; projection: " << cq->getQueryRequest().getProj()
<< "; collation: " << cq->getQueryRequest().getCollation() << ")";
return Status::OK();
}
LOG(1) << ns << ": removed plan cache entry - " << redact(cq->getQueryObj())
<< "(sort: " << cq->getQueryRequest().getSort()
<< "; projection: " << cq->getQueryRequest().getProj()
<< "; collation: " << cq->getQueryRequest().getCollation() << ")";
return Status::OK();
}
// If query is not provided, make sure sort, projection, and collation are not in arguments.
// We do not want to clear the entire cache inadvertently when the user
// forgets to provide a value for "query".
if (cmdObj.hasField("sort") || cmdObj.hasField("projection") || cmdObj.hasField("collation")) {
return Status(ErrorCodes::BadValue,
"sort, projection, or collation provided without query");
}
planCache->clear();
LOG(1) << ns << ": cleared plan cache";
return Status::OK();
}
PlanCacheListPlans::PlanCacheListPlans()
: PlanCacheCommand("planCacheListPlans",
"Displays the cached plans for a query shape.",
ActionType::planCacheRead) {}
Status PlanCacheListPlans::runPlanCacheCommand(OperationContext* opCtx,
const std::string& ns,
const BSONObj& cmdObj,
BSONObjBuilder* bob) {
AutoGetCollectionForReadCommand ctx(opCtx, NamespaceString(ns));
PlanCache* planCache;
uassertStatusOK(getPlanCache(opCtx, ctx.getCollection(), ns, &planCache));
return list(opCtx, *planCache, ns, cmdObj, bob);
}
namespace {
Status listPlansOriginalFormat(std::unique_ptr cq,
const PlanCache& planCache,
BSONObjBuilder* bob) {
auto lookupResult = planCache.getEntry(*cq);
if (lookupResult == ErrorCodes::NoSuchKey) {
// Return empty plans in results if query shape does not
// exist in plan cache.
BSONArrayBuilder plansBuilder(bob->subarrayStart("plans"));
plansBuilder.doneFast();
return Status::OK();
} else if (!lookupResult.isOK()) {
return lookupResult.getStatus();
}
auto entry = std::move(lookupResult.getValue());
BSONArrayBuilder plansBuilder(bob->subarrayStart("plans"));
size_t numPlans = entry->plannerData.size();
invariant(numPlans == entry->decision->stats.size());
invariant(numPlans == entry->decision->scores.size());
for (size_t i = 0; i < numPlans; ++i) {
BSONObjBuilder planBob(plansBuilder.subobjStart());
// 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());
detailsBob.doneFast();
// reason is comprised of score and initial stats provided by
// multi plan runner.
BSONObjBuilder reasonBob(planBob.subobjStart("reason"));
reasonBob.append("score", entry->decision->scores[i]);
BSONObjBuilder statsBob(reasonBob.subobjStart("stats"));
PlanStageStats* stats = entry->decision->stats[i].get();
if (stats) {
Explain::statsToBSON(*stats, &statsBob);
}
statsBob.doneFast();
reasonBob.doneFast();
// BSON object for 'feedback' field shows scores from historical executions of the plan.
BSONObjBuilder feedbackBob(planBob.subobjStart("feedback"));
if (i == 0U) {
feedbackBob.append("nfeedback", int(entry->feedback.size()));
BSONArrayBuilder scoresBob(feedbackBob.subarrayStart("scores"));
for (size_t i = 0; i < entry->feedback.size(); ++i) {
BSONObjBuilder scoreBob(scoresBob.subobjStart());
scoreBob.append("score", entry->feedback[i]);
}
scoresBob.doneFast();
}
feedbackBob.doneFast();
planBob.append("filterSet", scd->indexFilterApplied);
}
plansBuilder.doneFast();
// Append the time the entry was inserted into the plan cache.
bob->append("timeOfCreation", entry->timeOfCreation);
bob->append("queryHash", unsignedIntToFixedLengthHex(entry->queryHash));
// Append whether or not the entry is active.
bob->append("isActive", entry->isActive);
bob->append("works", static_cast(entry->works));
return Status::OK();
}
} // namespace
// static
Status PlanCacheListPlans::list(OperationContext* opCtx,
const PlanCache& planCache,
const std::string& ns,
const BSONObj& cmdObj,
BSONObjBuilder* bob) {
auto statusWithCQ = canonicalize(opCtx, ns, cmdObj);
if (!statusWithCQ.isOK()) {
return statusWithCQ.getStatus();
}
if (!internalQueryCacheListPlansNewOutput.load())
return listPlansOriginalFormat(std::move(statusWithCQ.getValue()), planCache, bob);
unique_ptr cq = std::move(statusWithCQ.getValue());
auto entry = uassertStatusOK(planCache.getEntry(*cq));
// internalQueryCacheDisableInactiveEntries is True and we should use the new output format.
Explain::planCacheEntryToBSON(*entry, bob);
return Status::OK();
}
} // namespace mongo