summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorBenety Goh <benety@mongodb.com>2014-01-09 15:05:55 -0500
committerBenety Goh <benety@mongodb.com>2014-01-15 16:35:44 -0500
commit9b94912fe8847977c4d23a12604cb900e6f426b7 (patch)
tree688de4d301133aa1a409529926487349584c7204 /src/mongo/db
parent26a8aaf0e660c247c2667e0f182bc4964096735b (diff)
downloadmongo-9b94912fe8847977c4d23a12604cb900e6f426b7.tar.gz
SERVER-8871 admin hints
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/auth/action_types.txt1
-rw-r--r--src/mongo/db/auth/role_graph_builtin_roles.cpp1
-rw-r--r--src/mongo/db/catalog/collection_info_cache.cpp9
-rw-r--r--src/mongo/db/catalog/collection_info_cache.h10
-rw-r--r--src/mongo/db/commands/hint_commands.cpp407
-rw-r--r--src/mongo/db/commands/hint_commands.h167
-rw-r--r--src/mongo/db/commands/hint_commands_test.cpp297
-rw-r--r--src/mongo/db/commands/plan_cache_commands.cpp29
-rw-r--r--src/mongo/db/commands/plan_cache_commands.h6
-rw-r--r--src/mongo/db/commands/plan_cache_commands_test.cpp71
-rw-r--r--src/mongo/db/query/SConscript16
-rw-r--r--src/mongo/db/query/canonical_query.cpp2
-rw-r--r--src/mongo/db/query/canonical_query.h2
-rw-r--r--src/mongo/db/query/canonical_query_test.cpp2
-rw-r--r--src/mongo/db/query/get_runner.cpp46
-rw-r--r--src/mongo/db/query/get_runner.h11
-rw-r--r--src/mongo/db/query/get_runner_test.cpp144
-rw-r--r--src/mongo/db/query/multi_plan_runner.cpp2
-rw-r--r--src/mongo/db/query/plan_cache.cpp15
-rw-r--r--src/mongo/db/query/plan_cache.h28
-rw-r--r--src/mongo/db/query/query_planner.cpp8
-rw-r--r--src/mongo/db/query/query_planner_params.h5
-rw-r--r--src/mongo/db/query/query_settings.cpp166
-rw-r--r--src/mongo/db/query/query_settings.h145
24 files changed, 1493 insertions, 97 deletions
diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt
index ea7cc057d12..e8ee88b25f3 100644
--- a/src/mongo/db/auth/action_types.txt
+++ b/src/mongo/db/auth/action_types.txt
@@ -71,6 +71,7 @@
"logRotate",
"moveChunk",
"netstat",
+"planCacheHint", # view/update admin hints
"planCacheRead", # view contents of plan cache
"planCacheWrite", # clear cache, drop cache entry, pin/unpin/shun plans
"reIndex",
diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp
index de6c5f8d44f..9eec7fd9077 100644
--- a/src/mongo/db/auth/role_graph_builtin_roles.cpp
+++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp
@@ -159,6 +159,7 @@ namespace {
<< ActionType::createIndex
<< ActionType::indexStats
<< ActionType::enableProfiler
+ << ActionType::planCacheHint
<< ActionType::planCacheRead
<< ActionType::planCacheWrite
<< ActionType::reIndex
diff --git a/src/mongo/db/catalog/collection_info_cache.cpp b/src/mongo/db/catalog/collection_info_cache.cpp
index 49eca9cee2c..7f84c034bfc 100644
--- a/src/mongo/db/catalog/collection_info_cache.cpp
+++ b/src/mongo/db/catalog/collection_info_cache.cpp
@@ -45,13 +45,16 @@ namespace mongo {
CollectionInfoCache::CollectionInfoCache( Collection* collection )
: _collection( collection ),
_keysComputed( false ),
- _planCache(new PlanCache()) { }
+ _planCache(new PlanCache()),
+ _querySettings(new QuerySettings()) { }
void CollectionInfoCache::reset() {
Lock::assertWriteLocked( _collection->ns().ns() );
clearQueryCache();
_keysComputed = false;
_planCache->clear();
+ // query settings is not affected by info cache reset.
+ // admin hints should persist throughout life of collection
}
void CollectionInfoCache::computeIndexKeys() {
@@ -89,4 +92,8 @@ namespace mongo {
return _planCache.get();
}
+ QuerySettings* CollectionInfoCache::getQuerySettings() const {
+ return _querySettings.get();
+ }
+
}
diff --git a/src/mongo/db/catalog/collection_info_cache.h b/src/mongo/db/catalog/collection_info_cache.h
index 55d20781fcd..9e695c1cbb9 100644
--- a/src/mongo/db/catalog/collection_info_cache.h
+++ b/src/mongo/db/catalog/collection_info_cache.h
@@ -33,6 +33,7 @@
#include <boost/scoped_ptr.hpp>
#include "mongo/db/index_set.h"
+#include "mongo/db/query/query_settings.h"
#include "mongo/db/query/plan_cache.h"
namespace mongo {
@@ -62,6 +63,11 @@ namespace mongo {
*/
PlanCache* getPlanCache() const;
+ /**
+ * Get the QuerySettings for this collection.
+ */
+ QuerySettings* getQuerySettings() const;
+
// -------------------
/* get set of index keys for this namespace. handy to quickly check if a given
@@ -96,6 +102,10 @@ namespace mongo {
// A cache for query plans.
boost::scoped_ptr<PlanCache> _planCache;
+ // Query settings.
+ // Includes admin hints.
+ boost::scoped_ptr<QuerySettings> _querySettings;
+
void computeIndexKeys();
};
diff --git a/src/mongo/db/commands/hint_commands.cpp b/src/mongo/db/commands/hint_commands.cpp
new file mode 100644
index 00000000000..3f0fdbcdb33
--- /dev/null
+++ b/src/mongo/db/commands/hint_commands.cpp
@@ -0,0 +1,407 @@
+/**
+* Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+*
+* 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.
+*/
+
+#include <string>
+#include <sstream>
+
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/db/client.h"
+#include "mongo/db/catalog/database.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/commands/hint_commands.h"
+#include "mongo/db/commands/plan_cache_commands.h"
+#include "mongo/db/catalog/collection.h"
+
+namespace {
+
+ using std::string;
+ using std::vector;
+ using namespace mongo;
+
+ /**
+ * Releases memory for container of pointers.
+ * XXX: move elsewhere when something similar is available in util libraries.
+ */
+ template <typename T>
+ class ContainerPointersDeleter {
+ public:
+ ContainerPointersDeleter(T* container) : _container(container) {
+ invariant(_container);
+ }
+
+ ~ContainerPointersDeleter() {
+ for (typename T::const_iterator i = _container->begin();
+ i != _container->end(); ++i) {
+ invariant(*i);
+ delete *i;
+ }
+ _container->clear();
+ }
+ private:
+ MONGO_DISALLOW_COPYING(ContainerPointersDeleter);
+ T* _container;
+ };
+
+ /**
+ * Utility function to extract error code and message from status
+ * and append to BSON results.
+ */
+ void addStatus(const Status& status, BSONObjBuilder& builder) {
+ builder.append("ok", status.isOK() ? 1.0 : 0.0);
+ if (!status.isOK()) {
+ builder.append("code", status.code());
+ }
+ if (!status.reason().empty()) {
+ builder.append("errmsg", status.reason());
+ }
+ }
+
+ /**
+ * Retrieves a collection's query settings from the database.
+ */
+ Status getQuerySettings(Database* db, const string& ns, QuerySettings** querySettingsOut) {
+ invariant(db);
+
+ Collection* collection = db->getCollection(ns);
+ if (NULL == collection) {
+ return Status(ErrorCodes::BadValue, "no such collection");
+ }
+
+ CollectionInfoCache* infoCache = collection->infoCache();
+ invariant(infoCache);
+
+ QuerySettings* querySettings = infoCache->getQuerySettings();
+ invariant(querySettings);
+
+ *querySettingsOut = querySettings;
+
+ return Status::OK();
+ }
+
+ /**
+ * Retrieves a collection's plan cache from the database.
+ */
+ Status getPlanCache(Database* db, const string& ns, PlanCache** planCacheOut) {
+ invariant(db);
+
+ Collection* collection = db->getCollection(ns);
+ 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(SetupHintCommands, MONGO_NO_PREREQUISITES)(
+ InitializerContext* context) {
+
+ new ListHints();
+ new ClearHints();
+ new SetHint();
+
+ return Status::OK();
+ }
+
+} // namespace
+
+namespace mongo {
+
+ using std::string;
+ using std::stringstream;
+ using std::vector;
+ using boost::scoped_ptr;
+
+ HintCommand::HintCommand(const string& name, const string& helpText)
+ : Command(name),
+ helpText(helpText) { }
+
+ bool HintCommand::run(const string& dbname, BSONObj& cmdObj, int options,
+ string& errmsg, BSONObjBuilder& result, bool fromRepl) {
+ string ns = parseNs(dbname, cmdObj);
+
+ Status status = runHintCommand(ns, cmdObj, &result);
+
+ if (!status.isOK()) {
+ addStatus(status, result);
+ return false;
+ }
+
+ return true;
+ }
+
+ Command::LockType HintCommand::locktype() const {
+ return NONE;
+ }
+
+ bool HintCommand::slaveOk() const {
+ return false;
+ }
+
+ void HintCommand::help(stringstream& ss) const {
+ ss << helpText;
+ }
+
+ Status HintCommand::checkAuthForCommand(ClientBasic* client, const std::string& dbname,
+ const BSONObj& cmdObj) {
+ AuthorizationSession* authzSession = client->getAuthorizationSession();
+ ResourcePattern pattern = parseResourcePattern(dbname, cmdObj);
+
+ if (authzSession->isAuthorizedForActionsOnResource(pattern, ActionType::planCacheHint)) {
+ return Status::OK();
+ }
+
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+
+ ListHints::ListHints() : HintCommand("planCacheListHints",
+ "Displays admin hints for all query shapes in a collection.") { }
+
+ Status ListHints::runHintCommand(const string& ns, BSONObj& cmdObj, BSONObjBuilder* bob) {
+ // This is a read lock. The query settings is owned by the collection.
+ Client::ReadContext readCtx(ns);
+ Client::Context& ctx = readCtx.ctx();
+ QuerySettings* querySettings;
+ Status status = getQuerySettings(ctx.db(), ns, &querySettings);
+ if (!status.isOK()) {
+ return status;
+ }
+ return list(*querySettings, bob);
+ }
+
+ // static
+ Status ListHints::list(const QuerySettings& querySettings, BSONObjBuilder* bob) {
+ invariant(bob);
+
+ // Format of BSON result:
+ //
+ // {
+ // hints: [
+ // {
+ // query: <query>,
+ // sort: <sort>,
+ // projection: <projection>,
+ // indexes: [<index1>, <index2>, <index3>, ...]
+ // }
+ // }
+ BSONArrayBuilder hintsBuilder(bob->subarrayStart("hints"));
+ vector<AllowedIndexEntry*> entries = querySettings.getAllAllowedIndices();
+ // Frees resources in entries on destruction.
+ ContainerPointersDeleter<vector<AllowedIndexEntry*> > deleter(&entries);
+ for (vector<AllowedIndexEntry*>::const_iterator i = entries.begin();
+ i != entries.end(); ++i) {
+ AllowedIndexEntry* entry = *i;
+ invariant(entry);
+
+ BSONObjBuilder hintBob(hintsBuilder.subobjStart());
+ hintBob.append("query", entry->query);
+ hintBob.append("sort", entry->sort);
+ hintBob.append("projection", entry->projection);
+ BSONArrayBuilder indexesBuilder(hintBob.subarrayStart("indexes"));
+ for (vector<BSONObj>::const_iterator j = entry->indexKeyPatterns.begin();
+ j != entry->indexKeyPatterns.end(); ++j) {
+ const BSONObj& index = *j;
+ indexesBuilder.append(index);
+ }
+ indexesBuilder.doneFast();
+ }
+ hintsBuilder.doneFast();
+ return Status::OK();
+ }
+
+ ClearHints::ClearHints() : HintCommand("planCacheClearHints",
+ "Clears all admin hints for a single query shape or, "
+ "if the query shape is omitted, for the entire collection.") { }
+
+ Status ClearHints::runHintCommand(const string& ns, BSONObj& cmdObj, BSONObjBuilder* bob) {
+ // This is a read lock. The query settings is owned by the collection.
+ Client::ReadContext readCtx(ns);
+ Client::Context& ctx = readCtx.ctx();
+ QuerySettings* querySettings;
+ Status status = getQuerySettings(ctx.db(), ns, &querySettings);
+ if (!status.isOK()) {
+ return status;
+ }
+ PlanCache* planCache;
+ status = getPlanCache(ctx.db(), ns, &planCache);
+ if (!status.isOK()) {
+ return status;
+ }
+ return clear(querySettings, planCache, ns, cmdObj);
+ }
+
+ // static
+ Status ClearHints::clear(QuerySettings* querySettings, PlanCache* planCache,
+ const std::string& ns, const BSONObj& cmdObj) {
+ invariant(querySettings);
+
+ // According to the specification, the planCacheClearHints command runs in two modes:
+ // - clear all hints; or
+ // - clear hints for single query shape when a query shape is described in the
+ // command arguments.
+ if (cmdObj.hasField("query")) {
+ CanonicalQuery* cqRaw;
+ Status status = PlanCacheCommand::canonicalize(ns, cmdObj, &cqRaw);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+ querySettings->removeAllowedIndices(*cq);
+
+ // Remove entry from plan cache
+ planCache->remove(*cq);
+ return Status::OK();
+ }
+
+ // If query is not provided, make sure sort and projection are not in arguments.
+ // We do not want to clear the entire cache inadvertently when the user
+ // forgot to provide a value for "query".
+ if (cmdObj.hasField("sort") || cmdObj.hasField("projection")) {
+ return Status(ErrorCodes::BadValue, "sort or projection provided without query");
+ }
+
+ // Get entries from query settings. We need to remove corresponding entries from the plan
+ // cache shortly.
+ vector<AllowedIndexEntry*> entries = querySettings->getAllAllowedIndices();
+ // Frees resources in entries on destruction.
+ ContainerPointersDeleter<vector<AllowedIndexEntry*> > deleter(&entries);
+
+ // OK to proceed with clearing entire cache.
+ querySettings->clearAllowedIndices();
+
+ // Remove corresponding entries from plan cache.
+ // Admin hints affect the planning process directly. If there were
+ // plans generated as a result of applying admin hints, these need to be
+ // invalidated. This allows the planner to re-populate the plan cache with
+ // non-admin hinted solutions next time the query is run.
+ // Resolve plan cache key from (query, sort, projection) in query settings entry.
+ // Concurrency note: There's no harm in removing plan cache entries one at at time.
+ // Only way that PlanCache::remove() can fail is when the query shape has been removed from
+ // the cache by some other means (re-index, collection info reset, ...). This is OK since
+ // that's the intended effect of calling the remove() function with the key from the hint entry.
+ for (vector<AllowedIndexEntry*>::const_iterator i = entries.begin();
+ i != entries.end(); ++i) {
+ AllowedIndexEntry* entry = *i;
+ invariant(entry);
+
+ // Create canonical query.
+ CanonicalQuery* cqRaw;
+ Status result = CanonicalQuery::canonicalize(ns, entry->query, entry->sort,
+ entry->projection, &cqRaw);
+ invariant(result.isOK());
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Remove plan cache entry.
+ planCache->remove(*cq);
+ }
+
+ return Status::OK();
+ }
+
+ SetHint::SetHint() : HintCommand("planCacheSetHint",
+ "Sets admin hints for a query shape. Overrides existing hints.") { }
+
+ Status SetHint::runHintCommand(const string& ns, BSONObj& cmdObj, BSONObjBuilder* bob) {
+ // This is a read lock. The query settings is owned by the collection.
+ Client::ReadContext readCtx(ns);
+ Client::Context& ctx = readCtx.ctx();
+ QuerySettings* querySettings;
+ Status status = getQuerySettings(ctx.db(), ns, &querySettings);
+ if (!status.isOK()) {
+ return status;
+ }
+ PlanCache* planCache;
+ status = getPlanCache(ctx.db(), ns, &planCache);
+ if (!status.isOK()) {
+ return status;
+ }
+ return set(querySettings, planCache, ns, cmdObj);
+ }
+
+ // static
+ Status SetHint::set(QuerySettings* querySettings, PlanCache* planCache,
+ const string& ns, const BSONObj& cmdObj) {
+ // indexes - required
+ BSONElement indexesElt = cmdObj.getField("indexes");
+ if (indexesElt.eoo()) {
+ return Status(ErrorCodes::BadValue, "required field indexes missing");
+ }
+ if (indexesElt.type() != mongo::Array) {
+ return Status(ErrorCodes::BadValue, "required field indexes must be an array");
+ }
+ vector<BSONElement> indexesEltArray = indexesElt.Array();
+ if (indexesEltArray.empty()) {
+ return Status(ErrorCodes::BadValue,
+ "required field indexes must contain at least one index");
+ }
+ vector<BSONObj> indexes;
+ for (vector<BSONElement>::const_iterator i = indexesEltArray.begin();
+ i != indexesEltArray.end(); ++i) {
+ const BSONElement& elt = *i;
+ if (!elt.isABSONObj()) {
+ return Status(ErrorCodes::BadValue, "each item in indexes must be an object");
+ }
+ BSONObj obj = elt.Obj();
+ if (obj.isEmpty()) {
+ return Status(ErrorCodes::BadValue, "index specification cannot be empty");
+ }
+ indexes.push_back(obj.copy());
+ }
+
+ CanonicalQuery* cqRaw;
+ Status status = PlanCacheCommand::canonicalize(ns, cmdObj, &cqRaw);
+ if (!status.isOK()) {
+ return status;
+ }
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Add allowed indices to query settings, overriding any previous entries.
+ querySettings->setAllowedIndices(*cq, indexes);
+
+ // Remove entry from plan cache.
+ planCache->remove(*cq);
+
+ return Status::OK();
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/hint_commands.h b/src/mongo/db/commands/hint_commands.h
new file mode 100644
index 00000000000..c0729560551
--- /dev/null
+++ b/src/mongo/db/commands/hint_commands.h
@@ -0,0 +1,167 @@
+/**
+ * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "mongo/db/commands.h"
+#include "mongo/db/query/query_settings.h"
+#include "mongo/db/query/plan_cache.h"
+
+namespace mongo {
+
+ /**
+ * DB commands for admin hints.
+ * Admin hint commands work on a different data structure in the collection
+ * info cache from the plan cache.
+ * The user still thinks of admin hint commands as part of the plan cache functionality
+ * so the command name prefix is still "planCache".
+ *
+ * These are in a header to facilitate unit testing. See hint_commands_test.cpp.
+ */
+
+ /**
+ * HintCommand
+ * Defines common attributes for all admin hint related commands
+ * such as slaveOk and locktype.
+ */
+ class HintCommand : public Command {
+ public:
+ HintCommand(const std::string& name, const std::string& helpText);
+
+ /**
+ * Entry point from command subsystem.
+ * Implementation provides standardization of error handling
+ * such as adding error code and message to BSON result.
+ *
+ * Do not override in derived classes.
+ * Override runPlanCacheCommands instead to
+ * implement plan cache command functionality.
+ */
+
+ bool run(const std::string& dbname, BSONObj& cmdObj, int options,
+ std::string& errmsg, BSONObjBuilder& result, bool fromRepl);
+
+ /**
+ * It's fine to return NONE here because plan cache commands
+ * create explicit read context to access collection info cache.
+ * Refer to dbcommands.cpp on how locktype() is handled.
+ */
+ virtual LockType locktype() const;
+
+ virtual bool slaveOk() const;
+
+ virtual void help(std::stringstream& ss) const;
+
+ /**
+ * One action type defined for hint commands:
+ * - adminHintReadWrite
+ */
+ virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname,
+ const BSONObj& cmdObj);
+
+ /**
+ * Subset of command arguments used by hint commands
+ * Override to provide command functionality.
+ * Should contain just enough logic to invoke run*Command() function
+ * in admin_hint.h
+ */
+ virtual Status runHintCommand(const std::string& ns, BSONObj& cmdObj,
+ BSONObjBuilder* bob) = 0;
+
+ private:
+ std::string helpText;
+ };
+
+ /**
+ * ListHints
+ *
+ * { planCacheListHints: <collection> }
+ *
+ */
+ class ListHints : public HintCommand {
+ public:
+ ListHints();
+
+ virtual Status runHintCommand(const std::string& ns, BSONObj& cmdObj, BSONObjBuilder* bob);
+
+ /**
+ * Looks up admin hints from collection's query settings.
+ * Inserts hints into BSON builder.
+ */
+ static Status list(const QuerySettings& querySettings, BSONObjBuilder* bob);
+ };
+
+ /**
+ * ClearHints
+ *
+ * { planCacheClearHints: <collection>, query: <query>, sort: <sort>, projection: <projection> }
+ *
+ */
+ class ClearHints : public HintCommand {
+ public:
+ ClearHints();
+
+ virtual Status runHintCommand(const std::string& ns, BSONObj& cmdObj, BSONObjBuilder* bob);
+
+ /**
+ * If query shape is provided, clears hints for a query.
+ * Otherwise, clears collection's query settings.
+ * Namespace argument ns is ignored if we are clearing the entire cache.
+ * Removes corresponding entries from plan cache.
+ */
+ static Status clear(QuerySettings* querySettings, PlanCache* planCache, const std::string& ns,
+ const BSONObj& cmdObj);
+ };
+
+ /**
+ * SetHint
+ *
+ * {
+ * planCacheSetHint: <collection>,
+ * query: <query>,
+ * sort: <sort>,
+ * projection: <projection>,
+ * indexes: [ <index1>, <index2>, <index3>, ... ]
+ * }
+ *
+ */
+ class SetHint : public HintCommand {
+ public:
+ SetHint();
+
+ virtual Status runHintCommand(const std::string& ns, BSONObj& cmdObj, BSONObjBuilder* bob);
+
+ /**
+ * Sets admin hints for a query shape.
+ * Removes entry for query shape from plan cache.
+ */
+ static Status set(QuerySettings* querySettings, PlanCache* planCache, const std::string& ns,
+ const BSONObj& cmdObj);
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/hint_commands_test.cpp b/src/mongo/db/commands/hint_commands_test.cpp
new file mode 100644
index 00000000000..33d911c25dc
--- /dev/null
+++ b/src/mongo/db/commands/hint_commands_test.cpp
@@ -0,0 +1,297 @@
+/**
+ * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+/**
+ * This file contains tests for mongo/db/commands/hint_commands.h
+ */
+
+#include "mongo/db/commands/hint_commands.h"
+
+#include "mongo/db/json.h"
+#include "mongo/db/query/plan_ranker.h"
+#include "mongo/db/query/query_solution.h"
+#include "mongo/unittest/unittest.h"
+
+using namespace mongo;
+
+namespace {
+
+ using std::string;
+ using std::vector;
+
+ static const char* ns = "somebogusns";
+
+ /**
+ * Utility function to get list of hints from the query settings.
+ */
+ vector<BSONObj> getHints(const QuerySettings& querySettings) {
+ BSONObjBuilder bob;
+ ASSERT_OK(ListHints::list(querySettings, &bob));
+ BSONObj resultObj = bob.obj();
+ BSONElement hintsElt = resultObj.getField("hints");
+ ASSERT_EQUALS(hintsElt.type(), mongo::Array);
+ vector<BSONElement> hintsEltArray = hintsElt.Array();
+ vector<BSONObj> hints;
+ for (vector<BSONElement>::const_iterator i = hintsEltArray.begin();
+ i != hintsEltArray.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());
+
+ // indexes
+ BSONElement indexesElt = obj.getField("indexes");
+ ASSERT_EQUALS(indexesElt.type(), mongo::Array);
+
+ // All fields OK. Append to vector.
+ hints.push_back(obj.copy());
+ }
+
+ return hints;
+ }
+
+ /**
+ * Injects an entry into plan cache for query shape.
+ */
+ void addQueryShapeToPlanCache(PlanCache* planCache, const char* queryStr, const char* sortStr,
+ const char* projectionStr) {
+ BSONObj queryObj = fromjson(queryStr);
+ BSONObj sortObj = fromjson(sortStr);
+ BSONObj projectionObj = fromjson(projectionStr);
+
+ // Create canonical query.
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, queryObj, sortObj, projectionObj, &cqRaw));
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+
+ QuerySolution qs;
+ qs.cacheData.reset(new SolutionCacheData());
+ qs.cacheData->tree.reset(new PlanCacheIndexTree());
+ std::vector<QuerySolution*> solns;
+ solns.push_back(&qs);
+ ASSERT_OK(planCache->add(*cq, solns, new PlanRankingDecision()));
+ }
+
+ /**
+ * Checks if plan cache contains query shape.
+ */
+ bool planCacheContains(const PlanCache& planCache, const char* queryStr, const char* sortStr,
+ const char* projectionStr) {
+ BSONObj queryObj = fromjson(queryStr);
+ BSONObj sortObj = fromjson(sortStr);
+ BSONObj projectionObj = fromjson(projectionStr);
+
+ // Create canonical query.
+ CanonicalQuery* cqRaw;
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, queryObj, sortObj, projectionObj, &cqRaw));
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+
+ // Retrieve cached solutions from plan cache.
+ vector<CachedSolution*> solutions = planCache.getAllSolutions();
+
+ // Search keys.
+ bool found = false;
+ for (vector<CachedSolution*>::const_iterator i = solutions.begin(); i != solutions.end(); i++) {
+ CachedSolution* cs = *i;
+ const PlanCacheKey& currentKey = cs->key;
+ if (currentKey == cq->getPlanCacheKey()) {
+ found = true;
+ }
+ // Release resources for cached solution after extracting key.
+ delete cs;
+ }
+ return found;
+ }
+
+ /**
+ * Tests for ListHints
+ */
+
+ TEST(HintCommandsTest, ListHintsEmpty) {
+ QuerySettings empty;
+ vector<BSONObj> hints = getHints(empty);
+ ASSERT_TRUE(hints.empty());
+ }
+
+ /**
+ * Tests for ClearHints
+ */
+
+ TEST(HintCommandsTest, ClearHintsInvalidParameter) {
+ QuerySettings empty;
+ PlanCache planCache;
+ // If present, query has to be an object.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns, fromjson("{query: 1234}")));
+ // If present, sort must be an object.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, sort: 1234}")));
+ // If present, projection must be an object.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, projection: 1234}")));
+ // Query must pass canonicalization.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns,
+ fromjson("{query: {a: {$no_such_op: 1}}}")));
+ // Sort present without query is an error.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns, fromjson("{sort: {a: 1}}")));
+ // Projection present without query is an error.
+ ASSERT_NOT_OK(ClearHints::clear(&empty, &planCache, ns,
+ fromjson("{projection: {_id: 0, a: 1}}")));
+ }
+
+ TEST(HintCommandsTest, ClearNonexistentHint) {
+ QuerySettings querySettings;
+ PlanCache planCache;
+ ASSERT_OK(SetHint::set(&querySettings, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: [{a: 1}]}")));
+ vector<BSONObj> hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 1U);
+
+ // Clear nonexistent hint.
+ // Command should succeed and cache should remain unchanged.
+ ASSERT_OK(ClearHints::clear(&querySettings, &planCache, ns, fromjson("{query: {b: 1}}")));
+ hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 1U);
+ }
+
+ /**
+ * Tests for SetHint
+ */
+
+ TEST(HintCommandsTest, SetHintInvalidParameter) {
+ QuerySettings empty;
+ PlanCache planCache;
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns, fromjson("{}")));
+ // Missing required query field.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns, fromjson("{indexes: [{a: 1}]}")));
+ // Missing required indexes field.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns, fromjson("{query: {a: 1}}")));
+ // Query has to be an object.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: 1234, indexes: [{a: 1}, {b: 1}]}")));
+ // Indexes field has to be an array.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: 1234}")));
+ // Array indexes field cannot empty.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: []}")));
+ // Elements in indexes have to be objects.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: [{a: 1}, 99]}")));
+ // Objects in indexes cannot be empty.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: [{a: 1}, {}]}")));
+ // If present, sort must be an object.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, sort: 1234, indexes: [{a: 1}, {b: 1}]}")));
+ // If present, projection must be an object.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: 1}, projection: 1234, indexes: [{a: 1}, {b: 1}]}")));
+ // Query must pass canonicalization.
+ ASSERT_NOT_OK(SetHint::set(&empty, &planCache, ns,
+ fromjson("{query: {a: {$no_such_op: 1}}, indexes: [{a: 1}, {b: 1}]}")));
+ }
+
+ TEST(HintCommandsTest, SetAndClearHints) {
+ QuerySettings querySettings;
+ PlanCache planCache;
+
+ // Inject query shape into plan cache.
+ addQueryShapeToPlanCache(&planCache, "{a: 1, b: 1}", "{a: -1}", "{_id: 0, a: 1}");
+ ASSERT_TRUE(planCacheContains(planCache, "{a: 1, b: 1}", "{a: -1}", "{_id: 0, a: 1}"));
+
+ ASSERT_OK(SetHint::set(&querySettings, &planCache, ns,
+ fromjson("{query: {a: 1, b: 1}, sort: {a: -1}, projection: {_id: 0, a: 1}, "
+ "indexes: [{a: 1}]}")));
+ vector<BSONObj> hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 1U);
+
+ // Query shape should not exist in plan cache after hint is updated.
+ ASSERT_FALSE(planCacheContains(planCache, "{a: 1, b: 1}", "{a: -1}", "{_id: 0, a: 1}"));
+
+ // Value of entries in hints should match criteria in most recent query settings update.
+ ASSERT_EQUALS(hints[0].getObjectField("query"), fromjson("{a: 1, b: 1}"));
+ ASSERT_EQUALS(hints[0].getObjectField("sort"), fromjson("{a: -1}"));
+ ASSERT_EQUALS(hints[0].getObjectField("projection"), fromjson("{_id: 0, a: 1}"));
+
+ // Replacing the hint for the same query shape ({a: 1, b: 1} and {b: 2, a: 3}
+ // share same shape) should not change the query settings size.
+ ASSERT_OK(SetHint::set(&querySettings, &planCache, ns,
+ fromjson("{query: {b: 2, a: 3}, sort: {a: -1}, projection: {_id: 0, a: 1}, "
+ "indexes: [{a: 1, b: 1}]}")));
+ hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 1U);
+
+ // Add hint for different query shape.
+ ASSERT_OK(SetHint::set(&querySettings, &planCache, ns,
+ fromjson("{query: {b: 1}, indexes: [{b: 1}]}")));
+ hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 2U);
+
+ // Add hint for 3rd query shape. This is to prepare for ClearHint tests.
+ ASSERT_OK(SetHint::set(&querySettings, &planCache, ns,
+ fromjson("{query: {a: 1}, indexes: [{a: 1}]}")));
+ hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 3U);
+
+ // Add 2 entries to plan cache and check plan cache after clearing one/all hints.
+ addQueryShapeToPlanCache(&planCache, "{a: 1}", "{}", "{}");
+ addQueryShapeToPlanCache(&planCache, "{b: 1}", "{}", "{}");
+
+ // Clear single hint.
+ ASSERT_OK(ClearHints::clear(&querySettings, &planCache, ns,
+ fromjson("{query: {a: 1}}")));
+ hints = getHints(querySettings);
+ ASSERT_EQUALS(hints.size(), 2U);
+
+ // Query shape should not exist in plan cache after cleaing 1 hint.
+ ASSERT_FALSE(planCacheContains(planCache, "{a: 1}", "{}", "{}"));
+ ASSERT_TRUE(planCacheContains(planCache, "{b: 1}", "{}", "{}"));
+
+ // Clear all hints
+ ASSERT_OK(ClearHints::clear(&querySettings, &planCache, ns, fromjson("{}")));
+ hints = getHints(querySettings);
+ ASSERT_TRUE(hints.empty());
+
+ // {b: 1} should be gone from plan cache after flushing query settings.
+ ASSERT_FALSE(planCacheContains(planCache, "{b: 1}", "{}", "{}"));
+ }
+
+} // namespace
diff --git a/src/mongo/db/commands/plan_cache_commands.cpp b/src/mongo/db/commands/plan_cache_commands.cpp
index 31d75254b53..2e6f6c9ea06 100644
--- a/src/mongo/db/commands/plan_cache_commands.cpp
+++ b/src/mongo/db/commands/plan_cache_commands.cpp
@@ -136,7 +136,7 @@ namespace mongo {
}
void PlanCacheCommand::help(stringstream& ss) const {
- ss << helpText << endl;
+ ss << helpText;
}
Status PlanCacheCommand::checkAuthForCommand(ClientBasic* client, const std::string& dbname,
@@ -152,7 +152,8 @@ namespace mongo {
}
// static
- Status PlanCacheCommand::makeCacheKey(const string& ns, const BSONObj& cmdObj, PlanCacheKey* keyOut) {
+ Status PlanCacheCommand::canonicalize(const string& ns, const BSONObj& cmdObj,
+ CanonicalQuery** canonicalQueryOut) {
// query - required
BSONElement queryElt = cmdObj.getField("query");
if (queryElt.eoo()) {
@@ -192,12 +193,8 @@ namespace mongo {
if (!result.isOK()) {
return result;
}
- scoped_ptr<CanonicalQuery> cq(cqRaw);
-
- // Generate key
- PlanCacheKey key = cq->getPlanCacheKey();
- *keyOut = key;
+ *canonicalQueryOut = cqRaw;
return Status::OK();
}
@@ -288,13 +285,14 @@ namespace mongo {
// static
Status PlanCacheDrop::drop(PlanCache* planCache, const string& ns, const BSONObj& cmdObj) {
- PlanCacheKey key;
- Status status = makeCacheKey(ns, cmdObj, &key);
+ CanonicalQuery* cqRaw;
+ Status status = canonicalize(ns, cmdObj, &cqRaw);
if (!status.isOK()) {
return status;
}
- Status result = planCache->remove(key);
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
+ Status result = planCache->remove(*cq);
if (!result.isOK()) {
return result;
}
@@ -320,14 +318,15 @@ namespace mongo {
// static
Status PlanCacheListPlans::list(const PlanCache& planCache, const std::string& ns,
const BSONObj& cmdObj, BSONObjBuilder* bob) {
- PlanCacheKey key;
- Status status = makeCacheKey(ns, cmdObj, &key);
+ CanonicalQuery* cqRaw;
+ Status status = canonicalize(ns, cmdObj, &cqRaw);
if (!status.isOK()) {
return status;
}
+ scoped_ptr<CanonicalQuery> cq(cqRaw);
CachedSolution* crRaw;
- Status result = planCache.get(key, &crRaw);
+ Status result = planCache.get(*cq, &crRaw);
if (!result.isOK()) {
return result;
}
@@ -347,10 +346,12 @@ namespace mongo {
detailsBob.append("solution", scd->toString());
detailsBob.doneFast();
- // XXX: Fill in rest of fields with bogus data.
// XXX: Fix these field values once we have fleshed out cache entries.
+ // reason should contain initial plan stats and score from ranking process.
+ // feedback should contain execution stats from running the query to completion.
planBob.append("reason", BSONObj());
planBob.append("feedback", BSONObj());
+ planBob.append("hint", scd->adminHintApplied);
}
plansBuilder.doneFast();
diff --git a/src/mongo/db/commands/plan_cache_commands.h b/src/mongo/db/commands/plan_cache_commands.h
index a72a19de4bc..8cf4d38f9ca 100644
--- a/src/mongo/db/commands/plan_cache_commands.h
+++ b/src/mongo/db/commands/plan_cache_commands.h
@@ -89,10 +89,10 @@ namespace mongo {
BSONObjBuilder* bob) = 0;
/**
- * Parses query shape from command object and returns plan cache key.
+ * Validatess query shape from command object and returns canonical query.
*/
- static Status makeCacheKey(const std::string& ns, const BSONObj& cmdObj,
- PlanCacheKey* keyOut);
+ static Status canonicalize(const std::string& ns, const BSONObj& cmdObj,
+ CanonicalQuery** canonicalQueryOut);
private:
std::string helpText;
diff --git a/src/mongo/db/commands/plan_cache_commands_test.cpp b/src/mongo/db/commands/plan_cache_commands_test.cpp
index 16c94a55665..0b9c591e00e 100644
--- a/src/mongo/db/commands/plan_cache_commands_test.cpp
+++ b/src/mongo/db/commands/plan_cache_commands_test.cpp
@@ -135,9 +135,6 @@ namespace {
ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
auto_ptr<CanonicalQuery> cq(cqRaw);
- // Retrieve the cache key
- PlanCacheKey queryKey = cq->getPlanCacheKey();
-
// Plan cache with one entry
PlanCache planCache;
QuerySolution qs;
@@ -157,55 +154,41 @@ namespace {
* Mostly validation on the input parameters
*/
- /**
- * Utility function to generate a cache key from command object string.
- */
- PlanCacheKey generateKey(const char* cmdStr) {
- BSONObj cmdObj = fromjson(cmdStr);
- PlanCacheKey key;
- Status status = PlanCacheCommand::makeCacheKey(ns, cmdObj, &key);
- if (!status.isOK()) {
- mongoutils::str::stream ss;
- ss << "failed to generate cache key. cmdObj: " << cmdStr;
- FAIL(ss);
- }
- if (key.empty()) {
- mongoutils::str::stream ss;
- ss << "zero-length cache key generated. cmdObj: " << cmdStr;
- FAIL(ss);
- }
- return key;
- }
-
- TEST(PlanCacheCommandsTest, planCacheGenerateKey) {
+ TEST(PlanCacheCommandsTest, Canonicalize) {
// Invalid parameters
- PlanCacheKey ignored;
+ CanonicalQuery* cqRaw;
// Missing query field
- ASSERT_NOT_OK(PlanCacheCommand::makeCacheKey(ns, fromjson("{}"), &ignored));
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{}"), &cqRaw));
// Query needs to be an object
- ASSERT_NOT_OK(PlanCacheCommand::makeCacheKey(ns, fromjson("{query: 1}"), &ignored));
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{query: 1}"), &cqRaw));
// Sort needs to be an object
- ASSERT_NOT_OK(PlanCacheCommand::makeCacheKey(ns, fromjson("{query: {}, sort: 1}"),
- &ignored));
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{query: {}, sort: 1}"),
+ &cqRaw));
// Bad query (invalid sort order)
- ASSERT_NOT_OK(PlanCacheCommand::makeCacheKey(ns, fromjson("{query: {}, sort: {a: 0}}"),
- &ignored));
+ ASSERT_NOT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{query: {}, sort: {a: 0}}"),
+ &cqRaw));
// Valid parameters
- PlanCacheKey queryKey = generateKey("{query: {a: 1, b: 1}}");
+ ASSERT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{query: {a: 1, b: 1}}"), &cqRaw));
+ scoped_ptr<CanonicalQuery> query(cqRaw);
+
// Equivalent query should generate same key.
- PlanCacheKey equivQueryKey = generateKey("{query: {b: 1, a: 1}}");
- ASSERT_EQUALS(queryKey, equivQueryKey);
+ ASSERT_OK(PlanCacheCommand::canonicalize(ns, fromjson("{query: {b: 1, a: 1}}"), &cqRaw));
+ scoped_ptr<CanonicalQuery> equivQuery(cqRaw);
+ ASSERT_EQUALS(query->getPlanCacheKey(), equivQuery->getPlanCacheKey());
// Sort query should generate different key from unsorted query.
- PlanCacheKey sortQueryKey = generateKey("{query: {a: 1, b: 1}, sort: {a: 1}}");
- ASSERT_NOT_EQUALS(queryKey, sortQueryKey);
+ ASSERT_OK(PlanCacheCommand::canonicalize(ns,
+ fromjson("{query: {a: 1, b: 1}, sort: {a: 1}}"), &cqRaw));
+ scoped_ptr<CanonicalQuery> sortQuery(cqRaw);
+ ASSERT_NOT_EQUALS(query->getPlanCacheKey(), sortQuery->getPlanCacheKey());
// Projected query should generate different key from unprojected query.
- PlanCacheKey projectionQueryKey =
- generateKey("{query: {a: 1, b: 1}, projection: {_id: 0, a: 1}}");
- ASSERT_NOT_EQUALS(queryKey, projectionQueryKey);
+ ASSERT_OK(PlanCacheCommand::canonicalize(ns,
+ fromjson("{query: {a: 1, b: 1}, projection: {_id: 0, a: 1}}"), &cqRaw));
+ scoped_ptr<CanonicalQuery> projectionQuery(cqRaw);
+ ASSERT_NOT_EQUALS(query->getPlanCacheKey(), projectionQuery->getPlanCacheKey());
}
/**
@@ -234,10 +217,6 @@ namespace {
ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{b: 1}"), &cqRaw));
auto_ptr<CanonicalQuery> cqB(cqRaw);
- // Generate 2 cache keys.
- PlanCacheKey keyA = cqA->getPlanCacheKey();
- PlanCacheKey keyB = cqB->getPlanCacheKey();
-
// Create plan cache with 2 entries.
PlanCache planCache;
QuerySolution qs;
@@ -347,9 +326,6 @@ namespace {
ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
auto_ptr<CanonicalQuery> cq(cqRaw);
- // Retrieve the cache key
- PlanCacheKey queryKey = cq->getPlanCacheKey();
-
// Plan cache with one entry
PlanCache planCache;
QuerySolution qs;
@@ -369,9 +345,6 @@ namespace {
ASSERT_OK(CanonicalQuery::canonicalize(ns, fromjson("{a: 1}"), &cqRaw));
auto_ptr<CanonicalQuery> cq(cqRaw);
- // Retrieve the cache key
- PlanCacheKey queryKey = cq->getPlanCacheKey();
-
// Plan cache with one entry
PlanCache planCache;
QuerySolution qs;
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index f010afcdf15..1c70fb4676b 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -6,6 +6,7 @@ env.Library(
target='query_planner',
source=[
"canonical_query.cpp",
+ "query_settings.cpp",
"index_tag.cpp",
"parsed_projection.cpp",
"plan_cache.cpp",
@@ -47,6 +48,21 @@ env.Library(
],
)
+env.CppUnitTest(
+ target="get_runner_test",
+ source=[
+ "get_runner_test.cpp"
+ ],
+ LIBDEPS=[
+ "query",
+ "$BUILD_DIR/mongo/serveronly",
+ "$BUILD_DIR/mongo/coreserver",
+ "$BUILD_DIR/mongo/coredb",
+ "$BUILD_DIR/mongo/mocklib",
+ ],
+ NO_CRUTCH = True,
+)
+
env.Library(
target="index_bounds",
source=[
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index 4e3f7233503..0a06f47915a 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -261,7 +261,7 @@ namespace mongo {
return Status::OK();
}
- PlanCacheKey CanonicalQuery::getPlanCacheKey() const {
+ const PlanCacheKey& CanonicalQuery::getPlanCacheKey() const {
return _cacheKey;
}
diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h
index 4bd9400339f..292978b6582 100644
--- a/src/mongo/db/query/canonical_query.h
+++ b/src/mongo/db/query/canonical_query.h
@@ -87,7 +87,7 @@ namespace mongo {
/**
* Get the cache key for this canonical query.
*/
- PlanCacheKey getPlanCacheKey() const;
+ const PlanCacheKey& getPlanCacheKey() const;
// Debugging
string toString() const;
diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp
index 359a46ff35a..79146bb12b1 100644
--- a/src/mongo/db/query/canonical_query_test.cpp
+++ b/src/mongo/db/query/canonical_query_test.cpp
@@ -337,7 +337,7 @@ namespace {
const char* projStr,
const char *expectedStr) {
auto_ptr<CanonicalQuery> cq(canonicalize(queryStr, sortStr, projStr));
- PlanCacheKey key = cq->getPlanCacheKey();
+ const PlanCacheKey& key = cq->getPlanCacheKey();
PlanCacheKey expectedKey(expectedStr);
if (key == expectedKey) {
return;
diff --git a/src/mongo/db/query/get_runner.cpp b/src/mongo/db/query/get_runner.cpp
index fe70d5eac22..fd03213cbf0 100644
--- a/src/mongo/db/query/get_runner.cpp
+++ b/src/mongo/db/query/get_runner.cpp
@@ -32,6 +32,7 @@
#include "mongo/db/query/cached_plan_runner.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/eof_runner.h"
+#include "mongo/db/query/query_settings.h"
#include "mongo/db/query/idhack_runner.h"
#include "mongo/db/query/internal_plans.h"
#include "mongo/db/query/multi_plan_runner.h"
@@ -91,6 +92,33 @@ namespace mongo {
&& !query.getParsed().hasOption(QueryOption_CursorTailable);
}
+ // static
+ void filterAllowedIndexEntries(const AllowedIndices& allowedIndices,
+ std::vector<IndexEntry>* indexEntries) {
+ invariant(indexEntries);
+
+ // Filter index entries
+ // Check BSON objects in AllowedIndices::_indexKeyPatterns against IndexEntry::keyPattern.
+ // Removes IndexEntrys that do not match _indexKeyPatterns.
+ std::vector<IndexEntry> temp;
+ for (std::vector<IndexEntry>::const_iterator i = indexEntries->begin();
+ i != indexEntries->end(); ++i) {
+ const IndexEntry& indexEntry = *i;
+ for (std::vector<BSONObj>::const_iterator j = allowedIndices.indexKeyPatterns.begin();
+ j != allowedIndices.indexKeyPatterns.end(); ++j) {
+ const BSONObj& index = *j;
+ // Copy index entry to temp vector if found in query settings.
+ if (0 == indexEntry.keyPattern.woCompare(index)) {
+ temp.push_back(indexEntry);
+ break;
+ }
+ }
+ }
+
+ // Update results.
+ temp.swap(*indexEntries);
+ }
+
/**
* For a given query, get a runner. The runner could be a SingleSolutionRunner, a
* CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc.
@@ -144,6 +172,18 @@ namespace mongo {
}
}
+ // If query supports admin hint, filter params.indices by indexes in query settings.
+ QuerySettings* querySettings = collection->infoCache()->getQuerySettings();
+ AllowedIndices* allowedIndicesRaw;
+
+ // Filter index catalog if admin hint is specified for query.
+ // Also, signal to planner that application hint should be ignored.
+ if (querySettings->getAllowedIndices(*canonicalQuery, &allowedIndicesRaw)) {
+ boost::scoped_ptr<AllowedIndices> allowedIndices(allowedIndicesRaw);
+ filterAllowedIndexEntries(*allowedIndices, &plannerParams.indices);
+ plannerParams.adminHintApplied = true;
+ }
+
// Tailable: If the query requests tailable the collection must be capped.
if (canonicalQuery->getParsed().hasOption(QueryOption_CursorTailable)) {
if (!collection->isCapped()) {
@@ -210,7 +250,8 @@ namespace mongo {
WorkingSet* ws;
PlanStage* root;
verify(StageBuilder::build(*qs, &root, &ws));
- CachedPlanRunner* cpr = new CachedPlanRunner(canonicalQuery.release(), qs, root, ws);
+ CachedPlanRunner* cpr = new CachedPlanRunner(canonicalQuery.release(), qs,
+ root, ws);
if (NULL != backupQs) {
WorkingSet* backupWs;
@@ -263,6 +304,9 @@ namespace mongo {
for (size_t i = 0; i < solutions.size(); ++i) {
WorkingSet* ws;
PlanStage* root;
+ if (solutions[i]->cacheData.get()) {
+ solutions[i]->cacheData->adminHintApplied = plannerParams.adminHintApplied;
+ }
verify(StageBuilder::build(*solutions[i], &root, &ws));
// Takes ownership of all arguments.
mpr->addPlan(solutions[i], root, ws);
diff --git a/src/mongo/db/query/get_runner.h b/src/mongo/db/query/get_runner.h
index 87ca08b2c0e..acb25cb08bd 100644
--- a/src/mongo/db/query/get_runner.h
+++ b/src/mongo/db/query/get_runner.h
@@ -27,6 +27,7 @@
*/
#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/query_settings.h"
#include "mongo/db/query/runner.h"
namespace mongo {
@@ -34,6 +35,16 @@ namespace mongo {
class Collection;
/**
+ * Filter indexes retrieved from index catalog by
+ * allowed indices in query settings.
+ * Used by getRunner().
+ * This function is public to facilitate testing.
+ */
+ void filterAllowedIndexEntries(const AllowedIndices& allowedIndices,
+ std::vector<IndexEntry>* indexEntries);
+
+
+ /**
* Get a runner for a query. Takes ownership of rawCanonicalQuery.
*
* If the query is valid and a runner could be created, returns Status::OK()
diff --git a/src/mongo/db/query/get_runner_test.cpp b/src/mongo/db/query/get_runner_test.cpp
new file mode 100644
index 00000000000..19cebfd7252
--- /dev/null
+++ b/src/mongo/db/query/get_runner_test.cpp
@@ -0,0 +1,144 @@
+/**
+ * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+/**
+ * This file contains tests for mongo/db/query/get_runner.h
+ */
+
+#include "mongo/db/query/get_runner.h"
+
+#include "mongo/db/json.h"
+#include "mongo/db/query/query_settings.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/mongoutils/str.h"
+
+using namespace mongo;
+
+namespace {
+
+ using std::auto_ptr;
+
+ static const char* ns = "somebogusns";
+
+ /**
+ * Utility functions to create a CanonicalQuery
+ */
+ CanonicalQuery* canonicalize(const char* queryStr, const char* sortStr,
+ const char* projStr) {
+ BSONObj queryObj = fromjson(queryStr);
+ BSONObj sortObj = fromjson(sortStr);
+ BSONObj projObj = fromjson(projStr);
+ CanonicalQuery* cq;
+ Status result = CanonicalQuery::canonicalize(ns, queryObj, sortObj,
+ projObj,
+ &cq);
+ ASSERT_OK(result);
+ return cq;
+ }
+
+ //
+ // get_runner tests
+ //
+
+ //
+ // filterAllowedIndexEntries
+ //
+
+ /**
+ * Test function to check filterAllowedIndexEntries
+ */
+ void testAllowedIndices(const char* hintKeyPatterns[],
+ const char* indexCatalogKeyPatterns[],
+ const char* expectedFilteredKeyPatterns[]) {
+ QuerySettings querySettings;
+ AllowedIndices *allowedIndicesRaw;
+
+ // getAllowedIndices should return false when query shape is not yet in query settings.
+ auto_ptr<CanonicalQuery> cq(canonicalize("{a: 1}", "{}", "{}"));
+ ASSERT_FALSE(querySettings.getAllowedIndices(*cq, &allowedIndicesRaw));
+
+ // Add entry to query settings.
+ const PlanCacheKey& key = cq->getPlanCacheKey();
+ std::vector<BSONObj> indexKeyPatterns;
+ for (int i=0; hintKeyPatterns[i] != NULL; ++i) {
+ indexKeyPatterns.push_back(fromjson(hintKeyPatterns[i]));
+ }
+ querySettings.setAllowedIndices(*cq, indexKeyPatterns);
+
+ // Index entry vector should contain 1 entry after filtering.
+ ASSERT_TRUE(querySettings.getAllowedIndices(*cq, &allowedIndicesRaw));
+ ASSERT_FALSE(key.empty());
+ ASSERT(NULL != allowedIndicesRaw);
+ auto_ptr<AllowedIndices> allowedIndices(allowedIndicesRaw);
+
+ // Indexes from index catalog.
+ std::vector<IndexEntry> indexEntries;
+ for (int i=0; indexCatalogKeyPatterns[i] != NULL; ++i) {
+ indexEntries.push_back(IndexEntry(fromjson(indexCatalogKeyPatterns[i])));
+ }
+
+ // Apply filter in allowed indices.
+ filterAllowedIndexEntries(*allowedIndices, &indexEntries);
+ size_t numExpected = 0;
+ while (expectedFilteredKeyPatterns[numExpected] != NULL) {
+ ASSERT_LESS_THAN(numExpected, indexEntries.size());
+ ASSERT_EQUALS(indexEntries[numExpected].keyPattern,
+ fromjson(expectedFilteredKeyPatterns[numExpected]));
+ numExpected++;
+ }
+ ASSERT_EQUALS(indexEntries.size(), numExpected);
+ }
+
+ // Use of admin hint to select compound index over single key index.
+ TEST(GetRunnerTest, GetAllowedIndices) {
+ const char* hintKeyPatterns[] = {"{a: 1, b: 1}", NULL};
+ const char* indexCatalogKeyPatterns[] = {"{a: 1}", "{a: 1, b: 1}", "{a: 1, c: 1}", NULL};
+ const char* expectedFilteredKeyPatterns[] = {"{a: 1, b: 1}", NULL};
+ testAllowedIndices(hintKeyPatterns, indexCatalogKeyPatterns, expectedFilteredKeyPatterns);
+ }
+
+ // Settings admin hints which refer to non-existent indexes
+ // will effectively disregard the index catalog and
+ // result in the planner generating a collection scan.
+ TEST(GetRunnerTest, GetAllowedIndicesNonExistentIndexKeyPatterns) {
+ const char* hintKeyPatterns[] = {"{nosuchfield: 1}", NULL};
+ const char* indexCatalogKeyPatterns[] = {"{a: 1}", "{a: 1, b: 1}", "{a: 1, c: 1}", NULL};
+ const char* expectedFilteredKeyPatterns[] = {NULL};
+ testAllowedIndices(hintKeyPatterns, indexCatalogKeyPatterns, expectedFilteredKeyPatterns);
+ }
+
+ // This test case shows how to force query execution to use
+ // an index that orders items in descending order.
+ TEST(GetRunnerTest, GetAllowedIndicesDescendingOrder) {
+ const char* hintKeyPatterns[] = {"{a: -1}", NULL};
+ const char* indexCatalogKeyPatterns[] = {"{a: 1}", "{a: -1}", NULL};
+ const char* expectedFilteredKeyPatterns[] = {"{a: -1}", NULL};
+ testAllowedIndices(hintKeyPatterns, indexCatalogKeyPatterns, expectedFilteredKeyPatterns);
+ }
+
+} // namespace
diff --git a/src/mongo/db/query/multi_plan_runner.cpp b/src/mongo/db/query/multi_plan_runner.cpp
index 28f6dc326a4..d2905d8f647 100644
--- a/src/mongo/db/query/multi_plan_runner.cpp
+++ b/src/mongo/db/query/multi_plan_runner.cpp
@@ -276,7 +276,7 @@ namespace mongo {
Collection* collection = db->getCollection(_query->ns());
verify(NULL != collection);
PlanCache* cache = collection->infoCache()->getPlanCache();
- cache->remove(_query->getPlanCacheKey());
+ cache->remove(*_query);
_bestPlan.reset(_backupPlan);
_backupPlan = NULL;
diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp
index f66c69274e7..e994447f05e 100644
--- a/src/mongo/db/query/plan_cache.cpp
+++ b/src/mongo/db/query/plan_cache.cpp
@@ -34,6 +34,7 @@
#include "mongo/db/query/plan_ranker.h"
#include "mongo/db/query/query_solution.h"
#include "mongo/db/query/qlog.h"
+#include "mongo/util/mongoutils/str.h"
namespace mongo {
@@ -203,6 +204,7 @@ namespace mongo {
}
other->solnType = this->solnType;
other->wholeIXSolnDir = this->wholeIXSolnDir;
+ other->adminHintApplied = this->adminHintApplied;
return other;
}
@@ -244,7 +246,6 @@ namespace mongo {
return Status(ErrorCodes::BadValue, "no solutions provided");
}
- PlanCacheKey key = query.getPlanCacheKey();
PlanCacheEntry* entry = new PlanCacheEntry(solns, why);
const LiteParsedQuery& pq = query.getParsed();
entry->query = pq.getFilter().copy();
@@ -265,6 +266,7 @@ namespace mongo {
boost::lock_guard<boost::mutex> cacheLock(_cacheMutex);
// Replace existing entry.
typedef unordered_map<PlanCacheKey, PlanCacheEntry*>::const_iterator ConstIterator;
+ const PlanCacheKey& key = query.getPlanCacheKey();
ConstIterator i = _cache.find(key);
if (i != _cache.end()) {
PlanCacheEntry* previousEntry = i->second;
@@ -276,11 +278,7 @@ namespace mongo {
}
Status PlanCache::get(const CanonicalQuery& query, CachedSolution** crOut) const{
- PlanCacheKey key = query.getPlanCacheKey();
- return get(key, crOut);
- }
-
- Status PlanCache::get(const PlanCacheKey& key, CachedSolution** crOut) const {
+ const PlanCacheKey& key = query.getPlanCacheKey();
verify(crOut);
boost::lock_guard<boost::mutex> cacheLock(_cacheMutex);
@@ -297,14 +295,15 @@ namespace mongo {
return Status::OK();
}
- Status PlanCache::feedback(const PlanCacheKey& ck, PlanCacheEntryFeedback* feedback) {
+ Status PlanCache::feedback(const CanonicalQuery& cq, PlanCacheEntryFeedback* feedback) {
boost::lock_guard<boost::mutex> cacheLock(_cacheMutex);
return Status(ErrorCodes::BadValue, "not implemented yet");
}
- Status PlanCache::remove(const PlanCacheKey& ck) {
+ Status PlanCache::remove(const CanonicalQuery& canonicalQuery) {
boost::lock_guard<boost::mutex> cacheLock(_cacheMutex);
typedef unordered_map<PlanCacheKey, PlanCacheEntry*>::const_iterator ConstIterator;
+ const PlanCacheKey& ck = canonicalQuery.getPlanCacheKey();
ConstIterator i = _cache.find(ck);
if (i == _cache.end()) {
return Status(ErrorCodes::BadValue, "no such key in cache");
diff --git a/src/mongo/db/query/plan_cache.h b/src/mongo/db/query/plan_cache.h
index 94c12316ea3..0d9f11e373f 100644
--- a/src/mongo/db/query/plan_cache.h
+++ b/src/mongo/db/query/plan_cache.h
@@ -128,7 +128,8 @@ namespace mongo {
SolutionCacheData() :
tree(NULL),
solnType(USE_INDEX_TAGS_SOLN),
- wholeIXSolnDir(1) {
+ wholeIXSolnDir(1),
+ adminHintApplied(false) {
}
// Make a deep copy.
@@ -161,6 +162,9 @@ namespace mongo {
// a proxy for a collection scan. Used only
// for WHOLE_IXSCAN_SOLN.
int wholeIXSolnDir;
+
+ // True if admin hints were applied.
+ bool adminHintApplied;
};
class PlanCacheEntry;
@@ -268,6 +272,10 @@ namespace mongo {
/**
* Generates a key for a normalized (for caching) canonical query
* from the match expression and sort order.
+ * This is an expensive operation because it clones and sorts
+ * the expression tree in order to generate a string from
+ * the normalized expression tree. The string generation is also
+ * potentially expensive.
*/
static PlanCacheKey getPlanCacheKey(const CanonicalQuery& query);
@@ -302,35 +310,25 @@ namespace mongo {
Status get(const CanonicalQuery& query, CachedSolution** crOut) const;
/**
- * Look up the cached data access for the provided key.
- *
- * If there is no entry in the cache for the 'query', returns an error Status.
- *
- * If there is an entry in the cache, populates 'crOut' and returns Status::OK(). Caller
- * owns '*crOut'.
- */
- Status get(const PlanCacheKey& key, CachedSolution** crOut) const;
-
- /**
* When the CachedPlanRunner runs a plan out of the cache, we want to record data about the
* plan's performance. The CachedPlanRunner calls feedback(...) at the end of query
* execution in order to do this.
*
* Cache takes ownership of 'feedback'.
*
- * If the entry corresponding to 'ck' isn't in the cache anymore, the feedback is ignored
+ * If the entry corresponding to 'cq' isn't in the cache anymore, the feedback is ignored
* and an error Status is returned.
*
- * If the entry corresponding to 'ck' still exists, 'feedback' is added to the run
+ * If the entry corresponding to 'cq' still exists, 'feedback' is added to the run
* statistics about the plan. Status::OK() is returned.
*/
- Status feedback(const PlanCacheKey& ck, PlanCacheEntryFeedback* feedback);
+ Status feedback(const CanonicalQuery& cq, PlanCacheEntryFeedback* feedback);
/**
* Remove the entry corresponding to 'ck' from the cache. Returns Status::OK() if the plan
* was present and removed and an error status otherwise.
*/
- Status remove(const PlanCacheKey& ck);
+ Status remove(const CanonicalQuery& canonicalQuery);
/**
* Remove *all* entries.
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index bd8956cbf26..8629cd63157 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -439,7 +439,13 @@ namespace mongo {
vector<IndexEntry> relevantIndices;
// Hints require us to only consider the hinted index.
- BSONObj hintIndex = query.getParsed().getHint();
+ // If admin hints in the query settings were used to override
+ // the allowed indices for planning, we should not use the hinted index
+ // requested in the query.
+ BSONObj hintIndex;
+ if (!params.adminHintApplied) {
+ hintIndex = query.getParsed().getHint();
+ }
// Snapshot is a form of a hint. If snapshot is set, try to use _id index to make a real
// plan. If that fails, just scan the _id index.
diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h
index 6e2972604ef..8fb00b110f8 100644
--- a/src/mongo/db/query/query_planner_params.h
+++ b/src/mongo/db/query/query_planner_params.h
@@ -36,7 +36,7 @@
namespace mongo {
struct QueryPlannerParams {
- QueryPlannerParams() : options(DEFAULT) { }
+ QueryPlannerParams() : options(DEFAULT), adminHintApplied(false) { }
enum Options {
// You probably want to set this.
@@ -76,6 +76,9 @@ namespace mongo {
// stage. If we know the shard key, we can perform covering analysis instead of always
// forcing a fetch.
BSONObj shardKey;
+
+ // Were admin hints applied to indices?
+ bool adminHintApplied;
};
} // namespace mongo
diff --git a/src/mongo/db/query/query_settings.cpp b/src/mongo/db/query/query_settings.cpp
new file mode 100644
index 00000000000..35ab6de5694
--- /dev/null
+++ b/src/mongo/db/query/query_settings.cpp
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+#include "mongo/db/query/query_settings.h"
+#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/plan_cache.h"
+
+namespace mongo {
+
+ using std::vector;
+
+ //
+ // HintOverride
+ //
+
+ AllowedIndices::AllowedIndices(const std::vector<BSONObj>& indexKeyPatterns) {
+ for (std::vector<BSONObj>::const_iterator i = indexKeyPatterns.begin();
+ i != indexKeyPatterns.end(); ++i) {
+ const BSONObj& indexKeyPattern = *i;
+ this->indexKeyPatterns.push_back(indexKeyPattern.copy());
+ }
+ }
+
+ AllowedIndices::~AllowedIndices() { }
+
+ //
+ // AllowedIndexEntry
+ //
+
+ AllowedIndexEntry::AllowedIndexEntry(const BSONObj& query, const BSONObj& sort,
+ const BSONObj& projection,
+ const std::vector<BSONObj>& indexKeyPatterns)
+ : query(query.copy()),
+ sort(sort.copy()),
+ projection(projection.copy()) {
+ for (std::vector<BSONObj>::const_iterator i = indexKeyPatterns.begin();
+ i != indexKeyPatterns.end(); ++i) {
+ const BSONObj& indexKeyPattern = *i;
+ this->indexKeyPatterns.push_back(indexKeyPattern.copy());
+ }
+ }
+
+ AllowedIndexEntry::~AllowedIndexEntry() { }
+
+ AllowedIndexEntry* AllowedIndexEntry::clone() const {
+ AllowedIndexEntry* entry = new AllowedIndexEntry(query, sort, projection, indexKeyPatterns);
+ return entry;
+ }
+
+ //
+ // QuerySettings
+ //
+
+ QuerySettings::QuerySettings() { }
+
+ QuerySettings::~QuerySettings() {
+ _clear();
+ }
+
+ bool QuerySettings::getAllowedIndices(const CanonicalQuery& query,
+ AllowedIndices** allowedIndicesOut) const {
+ invariant(allowedIndicesOut);
+
+ const PlanCacheKey& key = query.getPlanCacheKey();
+
+ boost::lock_guard<boost::mutex> cacheLock(_mutex);
+ AllowedIndexEntryMap::const_iterator cacheIter = _allowedIndexEntryMap.find(key);
+
+ // Nothing to do if key does not exist in query settings.
+ if (cacheIter == _allowedIndexEntryMap.end()) {
+ *allowedIndicesOut = NULL;
+ return false;
+ }
+
+ AllowedIndexEntry* entry = cacheIter->second;
+
+ // Create a AllowedIndices from entry.
+ *allowedIndicesOut = new AllowedIndices(entry->indexKeyPatterns);
+
+ return true;
+ }
+
+ std::vector<AllowedIndexEntry*> QuerySettings::getAllAllowedIndices() const {
+ boost::lock_guard<boost::mutex> cacheLock(_mutex);
+ vector<AllowedIndexEntry*> entries;
+ for (AllowedIndexEntryMap::const_iterator i = _allowedIndexEntryMap.begin(); i != _allowedIndexEntryMap.end(); ++i) {
+ AllowedIndexEntry* entry = i->second;
+ entries.push_back(entry->clone());
+ }
+ return entries;
+ }
+
+ void QuerySettings::setAllowedIndices(const CanonicalQuery& canonicalQuery,
+ const std::vector<BSONObj>& indexes) {
+ const LiteParsedQuery& lpq = canonicalQuery.getParsed();
+ const BSONObj& query = lpq.getFilter();
+ const BSONObj& sort = lpq.getSort();
+ const BSONObj& projection = lpq.getProj();
+ AllowedIndexEntry* entry = new AllowedIndexEntry(query, sort, projection, indexes);
+
+ const PlanCacheKey& key = canonicalQuery.getPlanCacheKey();
+ boost::lock_guard<boost::mutex> cacheLock(_mutex);
+ AllowedIndexEntryMap::iterator i = _allowedIndexEntryMap.find(key);
+ // Replace existing entry.
+ if (i != _allowedIndexEntryMap.end()) {
+ AllowedIndexEntry* entry = i->second;
+ delete entry;
+ }
+ _allowedIndexEntryMap[key] = entry;
+ }
+
+ void QuerySettings::removeAllowedIndices(const CanonicalQuery& canonicalQuery) {
+ const PlanCacheKey& key = canonicalQuery.getPlanCacheKey();
+ boost::lock_guard<boost::mutex> cacheLock(_mutex);
+ AllowedIndexEntryMap::iterator i = _allowedIndexEntryMap.find(key);
+
+ // Nothing to do if key does not exist in query settings.
+ if (i == _allowedIndexEntryMap.end()) {
+ return;
+ }
+
+ // Free up resources and delete entry.
+ AllowedIndexEntry* entry = i->second;
+ _allowedIndexEntryMap.erase(i);
+ delete entry;
+ }
+
+ void QuerySettings::clearAllowedIndices() {
+ boost::lock_guard<boost::mutex> cacheLock(_mutex);
+ _clear();
+ }
+
+ void QuerySettings::_clear() {
+ for (AllowedIndexEntryMap::const_iterator i = _allowedIndexEntryMap.begin(); i != _allowedIndexEntryMap.end(); ++i) {
+ AllowedIndexEntry* entry = i->second;
+ delete entry;
+ }
+ _allowedIndexEntryMap.clear();
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/query/query_settings.h b/src/mongo/db/query/query_settings.h
new file mode 100644
index 00000000000..a25c8309947
--- /dev/null
+++ b/src/mongo/db/query/query_settings.h
@@ -0,0 +1,145 @@
+/**
+ * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <boost/thread/mutex.hpp>
+#include "mongo/base/disallow_copying.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/index_entry.h"
+#include "mongo/platform/unordered_map.h"
+
+namespace mongo {
+
+ /**
+ * Holds allowed indices.
+ */
+ class AllowedIndices {
+ private:
+ MONGO_DISALLOW_COPYING(AllowedIndices);
+ public:
+ AllowedIndices(const std::vector<BSONObj>& indexKeyPatterns);
+ ~AllowedIndices();
+
+ // These are the index key patterns that
+ // we will use to override the indexes retrieved from
+ // the index catalog.
+ std::vector<BSONObj> indexKeyPatterns;
+ };
+
+ /**
+ * Value type for query settings.
+ * Holds:
+ * query shape (query, sort, projection)
+ * vector of index specs
+ */
+ class AllowedIndexEntry {
+ private:
+ MONGO_DISALLOW_COPYING(AllowedIndexEntry);
+ public:
+ AllowedIndexEntry(const BSONObj& query, const BSONObj& sort,
+ const BSONObj& projection,
+ const std::vector<BSONObj>& indexKeyPatterns);
+ ~AllowedIndexEntry();
+ AllowedIndexEntry* clone() const;
+
+ // _query, _sort and _projection collectively
+ // represent the query shape that we are storing hint overrides for.
+ BSONObj query;
+ BSONObj sort;
+ BSONObj projection;
+
+ // These are the index key patterns that
+ // we will use to override the indexes retrieved from
+ // the index catalog.
+ std::vector<BSONObj> indexKeyPatterns;
+ };
+
+ /**
+ * Holds the admin hints in a collection.
+ */
+ class QuerySettings {
+ private:
+ MONGO_DISALLOW_COPYING(QuerySettings);
+ public:
+ QuerySettings();
+
+ ~QuerySettings();
+
+ /**
+ * Returns true and fills out allowedIndicesOut if a hint is set in the query settings
+ * for the query.
+ * Returns false and sets allowedIndicesOut to NULL otherwise.
+ * Caller owns AllowedIndices.
+ */
+ bool getAllowedIndices(const CanonicalQuery& query,
+ AllowedIndices** allowedIndicesOut) const;
+
+ /**
+ * Returns copies all overrides for the collection..
+ * Caller owns overrides in vector.
+ */
+ std::vector<AllowedIndexEntry*> getAllAllowedIndices() const;
+
+ /**
+ * Adds or replaces entry in query settings.
+ * If existing entry is found for the same key,
+ * frees resources for existing entry before replacing.
+ */
+ void setAllowedIndices(const CanonicalQuery& canonicalQuery,
+ const std::vector<BSONObj>& indexes);
+
+ /**
+ * Removes single entry from query settings. No effect if query shape is not found.
+ */
+ void removeAllowedIndices(const CanonicalQuery& canonicalQuery);
+
+ /**
+ * Clears all allowed indices from query settings.
+ */
+ void clearAllowedIndices();
+
+ private:
+ /**
+ * Clears entries without acquiring mutex.
+ */
+ void _clear();
+
+ typedef unordered_map<PlanCacheKey, AllowedIndexEntry*> AllowedIndexEntryMap;
+ AllowedIndexEntryMap _allowedIndexEntryMap;
+
+ /**
+ * Protects data in query settings.
+ */
+ mutable boost::mutex _mutex;
+ };
+
+} // namespace mongo