summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2014-09-03 16:39:56 -0400
committerDavid Storch <david.storch@10gen.com>2014-09-11 15:18:48 -0400
commitf5f42d6c684b8e779aef6889b800235d7417afcb (patch)
tree489e7394d816246b1a8496beaf5919c34fb81ca4 /src/mongo/db/ops
parent3e5710a29505883143feb0b0883321830c989820 (diff)
downloadmongo-f5f42d6c684b8e779aef6889b800235d7417afcb.tar.gz
SERVER-14101 explain for update
Diffstat (limited to 'src/mongo/db/ops')
-rw-r--r--src/mongo/db/ops/update.cpp107
-rw-r--r--src/mongo/db/ops/update.h14
-rw-r--r--src/mongo/db/ops/update_executor.cpp127
-rw-r--r--src/mongo/db/ops/update_executor.h20
-rw-r--r--src/mongo/db/ops/update_request.h19
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