diff options
author | David Storch <david.storch@10gen.com> | 2014-12-04 11:40:04 -0500 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2014-12-04 17:41:11 -0500 |
commit | 5080932a79ce0152535169ec3b42edde2951bdcd (patch) | |
tree | 3fd5abb6a6e31fffda93446c5382ed8a745b1a99 /src/mongo/db/ops | |
parent | 4062e8f9493d4a8f9c32aba70fb19217241850c9 (diff) | |
download | mongo-5080932a79ce0152535169ec3b42edde2951bdcd.tar.gz |
SERVER-16101 replace UpdateExecutor with ParsedUpdate
Diffstat (limited to 'src/mongo/db/ops')
-rw-r--r-- | src/mongo/db/ops/parsed_update.cpp | 132 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_update.h | 126 | ||||
-rw-r--r-- | src/mongo/db/ops/update.cpp | 48 | ||||
-rw-r--r-- | src/mongo/db/ops/update_executor.cpp | 326 | ||||
-rw-r--r-- | src/mongo/db/ops/update_executor.h | 149 |
5 files changed, 303 insertions, 478 deletions
diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp new file mode 100644 index 00000000000..19413b777fd --- /dev/null +++ b/src/mongo/db/ops/parsed_update.cpp @@ -0,0 +1,132 @@ +/** + * 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kWrites + +#include "mongo/platform/basic.h" + +#include "mongo/db/ops/parsed_update.h" +#include "mongo/db/ops/update_request.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/query_planner_common.h" + +namespace mongo { + + ParsedUpdate::ParsedUpdate(OperationContext* txn, const UpdateRequest* request) : + _txn(txn), + _request(request), + _driver(UpdateDriver::Options()), + _canonicalQuery() { } + + Status ParsedUpdate::parseRequest() { + // We parse the update portion before the query portion because the dispostion of the update + // may determine whether or not we need to produce a CanonicalQuery at all. For example, if + // the update involves the positional-dollar operator, we must have a CanonicalQuery even if + // it isn't required for query execution. + Status status = parseUpdate(); + if (!status.isOK()) + return status; + status = parseQuery(); + if (!status.isOK()) + return status; + return Status::OK(); + } + + Status ParsedUpdate::parseQuery() { + dassert(!_canonicalQuery.get()); + + if (!_driver.needMatchDetails() && CanonicalQuery::isSimpleIdQuery(_request->getQuery())) { + return Status::OK(); + } + + return parseQueryToCQ(); + } + + Status ParsedUpdate::parseQueryToCQ() { + dassert(!_canonicalQuery.get()); + + CanonicalQuery* cqRaw; + const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db()); + + Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(), + _request->getQuery(), + _request->isExplain(), + &cqRaw, + whereCallback); + if (status.isOK()) { + cqRaw->setIsForWrite(true); + _canonicalQuery.reset(cqRaw); + } + + return status; + } + + Status ParsedUpdate::parseUpdate() { + const NamespaceString& ns(_request->getNamespaceString()); + + // Should the modifiers validate their embedded docs via okForStorage + // Only user updates should be checked. Any system or replication stuff should pass through. + // Config db docs shouldn't get checked for valid field names since the shard key can have + // a dot (".") in it. + const bool shouldValidate = !(_request->isFromReplication() || + ns.isConfigDB() || + _request->isFromMigration()); + + _driver.setLogOp(true); + _driver.setModOptions(ModifierInterface::Options(_request->isFromReplication(), + shouldValidate)); + + return _driver.parse(_request->getUpdates(), _request->isMulti()); + } + + bool ParsedUpdate::canYield() const { + return !_request->isGod() && + PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() && ( + _canonicalQuery.get() ? + !QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC) : + !LiteParsedQuery::isQueryIsolated(_request->getQuery())); + } + + bool ParsedUpdate::hasParsedQuery() const { + return _canonicalQuery.get() != NULL; + } + + CanonicalQuery* ParsedUpdate::releaseParsedQuery() { + invariant(_canonicalQuery.get() != NULL); + return _canonicalQuery.release(); + } + + const UpdateRequest* ParsedUpdate::getRequest() const { + return _request; + } + + UpdateDriver* ParsedUpdate::getDriver() { + return &_driver; + } + +} // namespace mongo diff --git a/src/mongo/db/ops/parsed_update.h b/src/mongo/db/ops/parsed_update.h new file mode 100644 index 00000000000..f93a264b3b8 --- /dev/null +++ b/src/mongo/db/ops/parsed_update.h @@ -0,0 +1,126 @@ +/** + * 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/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/db/ops/update_driver.h" + +namespace mongo { + + class CanonicalQuery; + class OperationContext; + class UpdateRequest; + + /** + * This class takes a pointer to an UpdateRequest, and converts that request into a parsed form + * via the parseRequest() method. A ParsedUpdate can then be used to retrieve a PlanExecutor + * capable of executing the update. + * + * No locks need to be held during parsing. + * + * The query part of the update is parsed to a CanonicalQuery, and the update part is parsed + * using the UpdateDriver. + */ + class ParsedUpdate { + MONGO_DISALLOW_COPYING(ParsedUpdate); + public: + /** + * Constructs a parsed update. + * + * The object pointed to by "request" must stay in scope for the life of the constructed + * ParsedUpdate. + */ + ParsedUpdate(OperationContext* txn, const UpdateRequest* request); + + /** + * Parses the update request to a canonical query and an update driver. On success, the + * parsed update can be used to create a PlanExecutor for this update. + */ + Status parseRequest(); + + /** + * As an optimization, we do not create a canonical query if the predicate is a simple + * _id equality. This method can be used to force full parsing to a canonical query, + * as a fallback if the idhack path is not available (e.g. no _id index). + */ + Status parseQueryToCQ(); + + /** + * Get the raw request. + */ + const UpdateRequest* getRequest() const; + + /** + * Get a pointer to the update driver, the abstraction which both parses the update and + * is capable of applying mods / computing damages. + */ + UpdateDriver* getDriver(); + + /** + * Is this update allowed to yield? + */ + bool canYield() const; + + /** + * As an optimization, we don't create a canonical query for updates with simple _id + * queries. Use this method to determine whether or not we actually parsed the query. + */ + bool hasParsedQuery() const; + + /** + * Releases ownership of the canonical query to the caller. + */ + CanonicalQuery* releaseParsedQuery(); + + private: + /** + * Parses the query portion of the update request. + */ + Status parseQuery(); + + /** + * Parses the update-descriptor portion of the update request. + */ + Status parseUpdate(); + + // Unowned pointer to the transactional context. + OperationContext* _txn; + + // Unowned pointer to the request object to process. + const UpdateRequest* const _request; + + // Driver for processing updates on matched documents. + UpdateDriver _driver; + + // Parsed query object, or NULL if the query proves to be an id hack query. + std::auto_ptr<CanonicalQuery> _canonicalQuery; + }; + +} // namespace mongo diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index 66f797458eb..12b2303ccc2 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -38,13 +38,14 @@ #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/clientcursor.h" +#include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/exec/update.h" #include "mongo/db/operation_context_impl.h" #include "mongo/db/ops/update_driver.h" -#include "mongo/db/ops/update_executor.h" #include "mongo/db/ops/update_lifecycle.h" #include "mongo/db/query/explain.h" #include "mongo/db/query/get_executor.h" +#include "mongo/db/repl/oplog.h" #include "mongo/db/update_index_data.h" #include "mongo/util/log.h" @@ -54,9 +55,50 @@ namespace mongo { Database* db, const UpdateRequest& request, OpDebug* opDebug) { + invariant(db); - UpdateExecutor executor(txn, &request, opDebug); - return executor.execute(db); + // Explain should never use this helper. + invariant(!request.isExplain()); + + const NamespaceString& nsString = request.getNamespaceString(); + Collection* collection = db->getCollection(txn, nsString.ns()); + + // The update stage does not create its own collection. As such, if the update is + // an upsert, create the collection that the update stage inserts into beforehand. + if (!collection && request.isUpsert()) { + // We have to have an exclusive lock on the db to be allowed to create the collection. + // Callers should either get an X or create the collection. + const Locker* locker = txn->lockState(); + invariant(locker->isW() || + locker->isLockHeldForMode(ResourceId(RESOURCE_DATABASE, nsString.db()), + MODE_X)); + + ScopedTransaction transaction(txn, MODE_IX); + Lock::DBLock lk(txn->lockState(), nsString.db(), MODE_X); + + WriteUnitOfWork wuow(txn); + collection = db->createCollection(txn, nsString.ns()); + invariant(collection); + + if (!request.isFromReplication()) { + repl::logOp(txn, + "c", + (db->name() + ".$cmd").c_str(), + BSON("create" << (nsString.coll()))); + } + wuow.commit(); + } + + // Parse the update, get an executor for it, run the executor, get stats out. + ParsedUpdate parsedUpdate(txn, &request); + uassertStatusOK(parsedUpdate.parseRequest()); + + PlanExecutor* rawExec; + uassertStatusOK(getExecutorUpdate(txn, collection, &parsedUpdate, opDebug, &rawExec)); + boost::scoped_ptr<PlanExecutor> exec(rawExec); + + uassertStatusOK(exec->executePlan()); + return UpdateStage::makeUpdateResult(exec.get(), opDebug); } BSONObj applyUpdateOperators(const BSONObj& from, const BSONObj& operators) { diff --git a/src/mongo/db/ops/update_executor.cpp b/src/mongo/db/ops/update_executor.cpp deleted file mode 100644 index 23645123af9..00000000000 --- a/src/mongo/db/ops/update_executor.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/** - * 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. - */ - -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kWrite - -#include "mongo/platform/basic.h" - -#include "mongo/db/ops/update_executor.h" - -#include "mongo/db/catalog/database.h" -#include "mongo/db/concurrency/d_concurrency.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/db/query/query_planner_common.h" -#include "mongo/db/repl/oplog.h" -#include "mongo/db/repl/repl_coordinator_global.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/fail_point_service.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(OperationContext* txn, const UpdateRequest* request, - OpDebug* opDebug) : - _txn(txn), - _request(request), - _opDebug(opDebug), - _driver(UpdateDriver::Options()), - _canonicalQuery(), - _isQueryParsed(false), - _isUpdateParsed(false) { - } - - UpdateExecutor::~UpdateExecutor() {} - - Status UpdateExecutor::prepare() { - // We parse the update portion before the query portion because the dispostion of the update - // may determine whether or not we need to produce a CanonicalQuery at all. For example, if - // the update involves the positional-dollar operator, we must have a CanonicalQuery even if - // it isn't required for query execution. - Status status = parseUpdate(); - if (!status.isOK()) - return status; - status = parseQuery(); - if (!status.isOK()) - return status; - return Status::OK(); - } - - PlanExecutor* UpdateExecutor::getPlanExecutor() { - return _exec.get(); - } - - MONGO_FP_DECLARE(implicitCollectionCreationDelay); - - 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(); - - validateUpdate(nsString.ns().c_str(), _request->getUpdates(), _request->getQuery()); - - // The batch executor is responsible for creating a database if this update is being - // performed against a non-existent database. However, it is possible for either the - // database or collection to be NULL for an explain. In this case, the explain is - // a no-op which returns a trivial EOF plan. - Collection* collection = NULL; - if (db) { - collection = db->getCollection(_txn, nsString.ns()); - } - else { - invariant(_request->isExplain()); - } - - // The update stage does not create its own collection. As such, if the update is - // an upsert, create the collection that the update stage inserts into beforehand. - // We can only create the collection if this is not an explain, as explains should not - // alter the state of the database. - if (!collection && _request->isUpsert() && !_request->isExplain()) { - // We have to have an exclsive lock on the db to be allowed to create the collection. - // Callers should either get an X or create the collection. - const Locker* locker = _txn->lockState(); - invariant( locker->isW() || - locker->isLockHeldForMode( ResourceId( RESOURCE_DATABASE, nsString.db() ), - MODE_X ) ); - - ScopedTransaction transaction(_txn, MODE_IX); - Lock::DBLock lk(_txn->lockState(), nsString.db(), MODE_X); - - WriteUnitOfWork wuow(_txn); - invariant(db->createCollection(_txn, nsString.ns())); - - if (!_request->isFromReplication()) { - repl::logOp(_txn, - "c", - (db->name() + ".$cmd").c_str(), - BSON("create" << (nsString.coll()))); - } - wuow.commit(); - collection = db->getCollection(_txn, nsString.ns()); - invariant(collection); - } - - // TODO: This seems a bit circuitious. - _opDebug->updateobj = _request->getUpdates(); - - // If this is a user-issued update, then we want to return an error: you cannot perform - // writes on a secondary. If this is an update to a secondary from the replication system, - // however, then we make an exception and let the write proceed. In this case, - // shouldCallLogOp() will be false. - if (_request->shouldCallLogOp() && - !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nsString.db())) { - return Status(ErrorCodes::NotMaster, - str::stream() << "Not primary while performing update on " - << nsString.ns()); - } - - if (lifecycle) { - lifecycle->setCollection(collection); - _driver.refreshIndexKeys(lifecycle->getIndexKeys(_txn)); - } - - // If yielding is allowed for this plan, then set an auto yield policy. Otherwise set - // a manual yield policy. - const bool canYield = !_request->isGod() && - PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() && ( - _canonicalQuery.get() ? - !QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC) : - !LiteParsedQuery::isQueryIsolated(_request->getQuery())); - - PlanExecutor::YieldPolicy policy = canYield ? PlanExecutor::YIELD_AUTO : - PlanExecutor::YIELD_MANUAL; - - PlanExecutor* rawExec = NULL; - Status getExecStatus = Status::OK(); - if (_canonicalQuery.get()) { - // This is the regular path for when we have a CanonicalQuery. - getExecStatus = getExecutorUpdate(_txn, - collection, - _canonicalQuery.release(), - _request, - &_driver, - _opDebug, - policy, - &rawExec); - } - else { - // This is the idhack fast-path for getting a PlanExecutor without doing the work - // to create a CanonicalQuery. - getExecStatus = getExecutorUpdate(_txn, - collection, - nsString.ns(), - _request, - &_driver, - _opDebug, - policy, - &rawExec); - } - - if (!getExecStatus.isOK()) { - return getExecStatus; - } - - invariant(rawExec); - _exec.reset(rawExec); - - return Status::OK(); - } - - UpdateResult UpdateExecutor::execute(Database* db) { - uassertStatusOK(prepare()); - - 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()); - - // 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() { - if (_isQueryParsed) - return Status::OK(); - - dassert(!_canonicalQuery.get()); - dassert(_isUpdateParsed); - - if (!_driver.needMatchDetails() && CanonicalQuery::isSimpleIdQuery(_request->getQuery())) { - _isQueryParsed = true; - return Status::OK(); - } - - CanonicalQuery* cqRaw; - const WhereCallbackReal whereCallback(_txn, _request->getNamespaceString().db()); - - Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(), - _request->getQuery(), - _request->isExplain(), - &cqRaw, - whereCallback); - if (status.isOK()) { - cqRaw->setIsForWrite( true ); - _canonicalQuery.reset(cqRaw); - _isQueryParsed = true; - } - - return status; - } - - Status UpdateExecutor::parseUpdate() { - if (_isUpdateParsed) - return Status::OK(); - - const NamespaceString& ns(_request->getNamespaceString()); - - // Should the modifiers validate their embedded docs via okForStorage - // Only user updates should be checked. Any system or replication stuff should pass through. - // Config db docs shouldn't get checked for valid field names since the shard key can have - // a dot (".") in it. - const bool shouldValidate = !(_request->isFromReplication() || - ns.isConfigDB() || - _request->isFromMigration()); - - _driver.setLogOp(true); - _driver.setModOptions(ModifierInterface::Options(_request->isFromReplication(), - shouldValidate)); - Status status = _driver.parse(_request->getUpdates(), _request->isMulti()); - if (status.isOK()) - _isUpdateParsed = true; - return status; - } - -} // namespace mongo diff --git a/src/mongo/db/ops/update_executor.h b/src/mongo/db/ops/update_executor.h deleted file mode 100644 index d0ef41083a2..00000000000 --- a/src/mongo/db/ops/update_executor.h +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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 <memory> - -#include "mongo/base/disallow_copying.h" -#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 { - - class CanonicalQuery; - class OpDebug; - class OperationContext; - class UpdateRequest; - - /** - * Implementation of the processing of an update operation in a mongod. - * - * The executor has two important methods, prepare() and execute(). The prepare() method can - * run without locks, and does whatever parsing and precomputation can be done without access to - * database data. The execute method performs the update, but the caller must already hold the - * appropriate database lock. - * - * Expected usage is approximately: - * UpdateRequest request(...); - * // configure request - * UpdateExecutor executor(txn, &request, opDebug); - * uassertStatusOK(executor.prepare()); - * // Get locks, get ready to execute. - * try { - * UpdateResult res = executor.execute(); - * } - * catch (const DBException& ex) { - * // Error handling. - * } - */ - class UpdateExecutor { - MONGO_DISALLOW_COPYING(UpdateExecutor); - public: - /** - * Constructs an update executor. - * - * The objects pointed to by "request" and "opDebug" must stay in scope for the life of the - * constructed executor. - */ - UpdateExecutor(OperationContext* txn, const UpdateRequest* request, OpDebug* opDebug); - - ~UpdateExecutor(); - - /** - * Performs preparatory work that does not require database locks. - * - * Returns Status::OK() on success. Other results indicate that the executor will not run - * correctly, and should be abandoned. - * - * Calling prepare() is optional. It is available for situations in which the user - * wishes to do as much work as possible before acquiring database locks. - */ - 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. - */ - UpdateResult execute(Database* db); - - private: - /** - * Parses the query portion of the update request. - */ - Status parseQuery(); - - /** - * Parses the update-descriptor portion of the update request. - */ - Status parseUpdate(); - - // Transactional context. Not owned by us. - OperationContext* _txn; - - // Unowned pointer to the request object that this executor will process. - const UpdateRequest* const _request; - - // Unowned pointer to the opdebug object that this executor will populate with debug data. - OpDebug* const _opDebug; - - // Driver for processing updates on matched documents. - UpdateDriver _driver; - - // 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; - - // Flag indicatin gif the update description has been successfully parsed. - bool _isUpdateParsed; - }; - -} // namespace mongo |