diff options
author | David Storch <david.storch@10gen.com> | 2014-09-03 16:39:56 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2014-09-11 15:18:48 -0400 |
commit | f5f42d6c684b8e779aef6889b800235d7417afcb (patch) | |
tree | 489e7394d816246b1a8496beaf5919c34fb81ca4 /src/mongo/db/ops | |
parent | 3e5710a29505883143feb0b0883321830c989820 (diff) | |
download | mongo-f5f42d6c684b8e779aef6889b800235d7417afcb.tar.gz |
SERVER-14101 explain for update
Diffstat (limited to 'src/mongo/db/ops')
-rw-r--r-- | src/mongo/db/ops/update.cpp | 107 | ||||
-rw-r--r-- | src/mongo/db/ops/update.h | 14 | ||||
-rw-r--r-- | src/mongo/db/ops/update_executor.cpp | 127 | ||||
-rw-r--r-- | src/mongo/db/ops/update_executor.h | 20 | ||||
-rw-r--r-- | src/mongo/db/ops/update_request.h | 19 |
5 files changed, 159 insertions, 128 deletions
diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index 301e89a13fd..435d4104df6 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -50,25 +50,6 @@ namespace mongo { - namespace { - - // TODO: Make this a function on NamespaceString, or make it cleaner. - inline void validateUpdate(const char* ns , - const BSONObj& updateobj, - const BSONObj& patternOrig) { - uassert(10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0); - if (strstr(ns, ".system.")) { - /* dm: it's very important that system.indexes is never updated as IndexDetails - has pointers into it */ - uassert(10156, - str::stream() << "cannot update system collection: " - << ns << " q: " << patternOrig << " u: " << updateobj, - legalClientSystemNS(ns , true)); - } - } - - } // namespace - UpdateResult update(Database* db, const UpdateRequest& request, OpDebug* opDebug) { @@ -77,94 +58,6 @@ namespace mongo { return executor.execute(db); } - UpdateResult update(Database* db, - const UpdateRequest& request, - OpDebug* opDebug, - UpdateDriver* driver, - CanonicalQuery* cq) { - - LOG(3) << "processing update : " << request; - - std::auto_ptr<CanonicalQuery> cqHolder(cq); - const NamespaceString& nsString = request.getNamespaceString(); - UpdateLifecycle* lifecycle = request.getLifecycle(); - - Collection* collection = db->getCollection(request.getOpCtx(), nsString.ns()); - - validateUpdate(nsString.ns().c_str(), request.getUpdates(), request.getQuery()); - - - // TODO: This seems a bit circuitious. - opDebug->updateobj = request.getUpdates(); - - if (lifecycle) { - lifecycle->setCollection(collection); - driver->refreshIndexKeys(lifecycle->getIndexKeys(request.getOpCtx())); - } - - PlanExecutor* rawExec; - Status status = Status::OK(); - if (cq) { - // This is the regular path for when we have a CanonicalQuery. - status = getExecutorUpdate(request.getOpCtx(), db, cqHolder.release(), &request, driver, - opDebug, &rawExec); - } - else { - // This is the idhack fast-path for getting a PlanExecutor without doing the work - // to create a CanonicalQuery. - status = getExecutorUpdate(request.getOpCtx(), db, nsString.ns(), &request, driver, - opDebug, &rawExec); - } - - uassert(17243, - "could not get executor" + request.getQuery().toString() + "; " + causedBy(status), - status.isOK()); - - // Create the plan executor and setup all deps. - scoped_ptr<PlanExecutor> exec(rawExec); - - // Register executor with the collection cursor cache. - const ScopedExecutorRegistration safety(exec.get()); - - // Run the plan (don't need to collect results because UpdateStage always returns - // NEED_TIME). - uassertStatusOK(exec->executePlan()); - - // Get stats from the root stage. - invariant(exec->getRootStage()->stageType() == STAGE_UPDATE); - UpdateStage* updateStage = static_cast<UpdateStage*>(exec->getRootStage()); - const UpdateStats* updateStats = - static_cast<const UpdateStats*>(updateStage->getSpecificStats()); - - // Use stats from the root stage to fill out opDebug. - opDebug->nMatched = updateStats->nMatched; - opDebug->nModified = updateStats->nModified; - opDebug->upsert = updateStats->inserted; - opDebug->fastmodinsert = updateStats->fastmodinsert; - opDebug->fastmod = updateStats->fastmod; - - // Historically, 'opDebug' considers 'nMatched' and 'nModified' to be 1 (rather than 0) if - // there is an upsert that inserts a document. The UpdateStage does not participate in this - // madness in order to have saner stats reporting for explain. This means that we have to - // set these values "manually" in the case of an insert. - if (updateStats->inserted) { - opDebug->nMatched = 1; - opDebug->nModified = 1; - } - - // Get summary information about the plan. - PlanSummaryStats stats; - Explain::getSummaryStats(exec.get(), &stats); - opDebug->nscanned = stats.totalKeysExamined; - opDebug->nscannedObjects = stats.totalDocsExamined; - - return UpdateResult(updateStats->nMatched > 0 /* Did we update at least one obj? */, - !driver->isDocReplacement() /* $mod or obj replacement */, - opDebug->nModified /* number of modified docs, no no-ops */, - opDebug->nMatched /* # of docs matched/updated, even no-ops */, - updateStats->objInserted); - } - BSONObj applyUpdateOperators(const BSONObj& from, const BSONObj& operators) { UpdateDriver::Options opts; UpdateDriver driver(opts); diff --git a/src/mongo/db/ops/update.h b/src/mongo/db/ops/update.h index cff79a0d90b..b28d722b28b 100644 --- a/src/mongo/db/ops/update.h +++ b/src/mongo/db/ops/update.h @@ -51,20 +51,6 @@ namespace mongo { OpDebug* opDebug); /** - * Execute the update described by "request", using the given already-parsed - * driver and canonical query. - * - * NOTE: This function is really a utility method for UpdateExecutor. - * - * TODO: Move this into a private method of UpdateExecutor. - */ - UpdateResult update(Database* db, - const UpdateRequest& request, - OpDebug* opDebug, - UpdateDriver* driver, - CanonicalQuery* cq); - - /** * takes the from document and returns a new document * after apply all the operators * e.g. diff --git a/src/mongo/db/ops/update_executor.cpp b/src/mongo/db/ops/update_executor.cpp index 268e7616d3c..b2f96860bb6 100644 --- a/src/mongo/db/ops/update_executor.cpp +++ b/src/mongo/db/ops/update_executor.cpp @@ -30,13 +30,38 @@ #include "mongo/db/ops/update_executor.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/exec/update.h" #include "mongo/db/ops/update.h" +#include "mongo/db/ops/update_driver.h" +#include "mongo/db/ops/update_lifecycle.h" #include "mongo/db/ops/update_request.h" #include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/get_executor.h" #include "mongo/util/assert_util.h" +#include "mongo/util/log.h" namespace mongo { + namespace { + + // TODO: Make this a function on NamespaceString, or make it cleaner. + inline void validateUpdate(const char* ns , + const BSONObj& updateobj, + const BSONObj& patternOrig) { + uassert(10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0); + if (strstr(ns, ".system.")) { + /* dm: it's very important that system.indexes is never updated as IndexDetails + has pointers into it */ + uassert(10156, + str::stream() << "cannot update system collection: " + << ns << " q: " << patternOrig << " u: " << updateobj, + legalClientSystemNS(ns , true)); + } + } + + } // namespace + UpdateExecutor::UpdateExecutor(const UpdateRequest* request, OpDebug* opDebug) : _request(request), _opDebug(opDebug), @@ -62,13 +87,105 @@ namespace mongo { return Status::OK(); } + PlanExecutor* UpdateExecutor::getPlanExecutor() { + return _exec.get(); + } + + Status UpdateExecutor::prepareInLock(Database* db) { + // If we have a non-NULL PlanExecutor, then we've already done the in-lock preparation. + if (_exec.get()) { + return Status::OK(); + } + + const NamespaceString& nsString = _request->getNamespaceString(); + UpdateLifecycle* lifecycle = _request->getLifecycle(); + + Collection* collection = db->getCollection(_request->getOpCtx(), nsString.ns()); + + validateUpdate(nsString.ns().c_str(), _request->getUpdates(), _request->getQuery()); + + // TODO: This seems a bit circuitious. + _opDebug->updateobj = _request->getUpdates(); + + if (lifecycle) { + lifecycle->setCollection(collection); + _driver.refreshIndexKeys(lifecycle->getIndexKeys(_request->getOpCtx())); + } + + PlanExecutor* rawExec = NULL; + Status getExecStatus = Status::OK(); + if (_canonicalQuery.get()) { + // This is the regular path for when we have a CanonicalQuery. + getExecStatus = getExecutorUpdate(_request->getOpCtx(), db, _canonicalQuery.release(), + _request, &_driver, _opDebug, &rawExec); + } + else { + // This is the idhack fast-path for getting a PlanExecutor without doing the work + // to create a CanonicalQuery. + getExecStatus = getExecutorUpdate(_request->getOpCtx(), db, nsString.ns(), _request, + &_driver, _opDebug, &rawExec); + } + + if (getExecStatus.isOK()) { + invariant(rawExec); + _exec.reset(rawExec); + } + + return getExecStatus; + } + UpdateResult UpdateExecutor::execute(Database* db) { uassertStatusOK(prepare()); - return update(db, - *_request, - _opDebug, - &_driver, - _canonicalQuery.release()); + + LOG(3) << "processing update : " << *_request; + + // If we've already done the in-lock preparation, this is a no-op. + Status status = prepareInLock(db); + uassert(17243, + "could not get executor " + _request->getQuery().toString() + + "; " + causedBy(status), + status.isOK()); + + // Register executor with the collection cursor cache. + const ScopedExecutorRegistration safety(_exec.get()); + + // Run the plan (don't need to collect results because UpdateStage always returns + // NEED_TIME). + uassertStatusOK(_exec->executePlan()); + + // Get stats from the root stage. + invariant(_exec->getRootStage()->stageType() == STAGE_UPDATE); + UpdateStage* updateStage = static_cast<UpdateStage*>(_exec->getRootStage()); + const UpdateStats* updateStats = + static_cast<const UpdateStats*>(updateStage->getSpecificStats()); + + // Use stats from the root stage to fill out opDebug. + _opDebug->nMatched = updateStats->nMatched; + _opDebug->nModified = updateStats->nModified; + _opDebug->upsert = updateStats->inserted; + _opDebug->fastmodinsert = updateStats->fastmodinsert; + _opDebug->fastmod = updateStats->fastmod; + + // Historically, 'opDebug' considers 'nMatched' and 'nModified' to be 1 (rather than 0) if + // there is an upsert that inserts a document. The UpdateStage does not participate in this + // madness in order to have saner stats reporting for explain. This means that we have to + // set these values "manually" in the case of an insert. + if (updateStats->inserted) { + _opDebug->nMatched = 1; + _opDebug->nModified = 1; + } + + // Get summary information about the plan. + PlanSummaryStats stats; + Explain::getSummaryStats(_exec.get(), &stats); + _opDebug->nscanned = stats.totalKeysExamined; + _opDebug->nscannedObjects = stats.totalDocsExamined; + + return UpdateResult(updateStats->nMatched > 0 /* Did we update at least one obj? */, + !_driver.isDocReplacement() /* $mod or obj replacement */, + _opDebug->nModified /* number of modified docs, no no-ops */, + _opDebug->nMatched /* # of docs matched/updated, even no-ops */, + updateStats->objInserted); } Status UpdateExecutor::parseQuery() { diff --git a/src/mongo/db/ops/update_executor.h b/src/mongo/db/ops/update_executor.h index f4a62224c59..9294de770d8 100644 --- a/src/mongo/db/ops/update_executor.h +++ b/src/mongo/db/ops/update_executor.h @@ -34,6 +34,7 @@ #include "mongo/base/status.h" #include "mongo/db/ops/update_driver.h" #include "mongo/db/ops/update_result.h" +#include "mongo/db/query/plan_executor.h" namespace mongo { @@ -88,6 +89,22 @@ namespace mongo { Status prepare(); /** + * Performs preparatory work that *does* require the appropriate database lock. This + * preparation involves construction of a PlanExecutor. Construction of a PlanExecutor + * requires the database lock because it goes through query planning and optimization, + * which may involve partial execution of the update plan tree. + * + * On success, a non-NULL PlanExecutor will be available via getPlanExecutor(). + */ + Status prepareInLock(Database* db); + + /** + * Retrieve the PlanExecutor that will be used to execute this update upon calling + * execute(). Returns NULL if no PlanExecutor has been created. + */ + PlanExecutor* getPlanExecutor(); + + /** * Execute an update. Requires the caller to hold the database lock on the * appropriate resources for the request. */ @@ -116,6 +133,9 @@ namespace mongo { /// Parsed query object, or NULL if the query proves to be an id hack query. std::auto_ptr<CanonicalQuery> _canonicalQuery; + // The tree of execution stages which will be used to execute the update. + boost::scoped_ptr<PlanExecutor> _exec; + /// Flag indicating if the query has been successfully parsed. bool _isQueryParsed; diff --git a/src/mongo/db/ops/update_request.h b/src/mongo/db/ops/update_request.h index 5ed6eca209a..8bc42db69d4 100644 --- a/src/mongo/db/ops/update_request.h +++ b/src/mongo/db/ops/update_request.h @@ -31,6 +31,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/curop.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/query/explain.h" #include "mongo/util/mongoutils/str.h" namespace mongo { @@ -51,7 +52,8 @@ namespace mongo { , _callLogOp(false) , _fromMigration(false) , _fromReplication(false) - , _lifecycle(NULL) {} + , _lifecycle(NULL) + , _isExplain(false) {} const NamespaceString& getNamespaceString() const { return _nsString; @@ -136,6 +138,14 @@ namespace mongo { return _txn; } + inline void setExplain(bool value = true) { + _isExplain = value; + } + + inline bool isExplain() const { + return _isExplain; + } + const std::string toString() const { return str::stream() << " query: " << _query @@ -145,7 +155,8 @@ namespace mongo { << " multi: " << _multi << " callLogOp: " << _callLogOp << " fromMigration: " << _fromMigration - << " fromReplications: " << _fromReplication; + << " fromReplications: " << _fromReplication + << " isExplain: " << _isExplain; } private: @@ -183,6 +194,10 @@ namespace mongo { // The lifecycle data, and events used during the update request. UpdateLifecycle* _lifecycle; + + // Whether or not we are requesting an explained update. Explained updates are read-only. + bool _isExplain; + }; } // namespace mongo |