/** * 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 . * * 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/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(const UpdateRequest* request, OpDebug* opDebug) : _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()); Collection* collection = db->getCollection(_request->getOpCtx(), 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()) { OperationContext* const txn = _request->getOpCtx(); // Upgrade to an exclusive lock. While this may possibly lead to a deadlock, // collection creation is rare and a retry will definitively succeed in this // case. Add a fail point to allow reliably triggering the deadlock situation. MONGO_FAIL_POINT_BLOCK(implicitCollectionCreationDelay, data) { LOG(0) << "Sleeping for creation of collection " + nsString.ns(); sleepmillis(1000); LOG(0) << "About to upgrade to exclusive lock on " + nsString.ns(); } 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(_request->getOpCtx(), 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(_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()) { return getExecStatus; } invariant(rawExec); _exec.reset(rawExec); // If yielding is allowed for this plan, then set an auto yield policy. Otherwise set // a manual yield policy. const bool canYield = !_request->isGod() && ( _canonicalQuery.get() ? !QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC) : !LiteParsedQuery::isQueryIsolated(_request->getQuery())); PlanExecutor::YieldPolicy policy = canYield ? PlanExecutor::YIELD_AUTO : PlanExecutor::YIELD_MANUAL; _exec->setYieldPolicy(policy); 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(_exec->getRootStage()); const UpdateStats* updateStats = static_cast(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( _request->getOpCtx(), _request->getNamespaceString().db()); Status status = CanonicalQuery::canonicalize(_request->getNamespaceString().ns(), _request->getQuery(), _request->isExplain(), &cqRaw, whereCallback); if (status.isOK()) { _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