diff options
author | David Storch <david.storch@10gen.com> | 2014-12-02 20:10:01 -0500 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2014-12-04 17:41:11 -0500 |
commit | 367810995073e01ee58159deb1bb5b878882632f (patch) | |
tree | 3074960c42a7b63e4e3be337e44f33a036a6679f /src | |
parent | 5080932a79ce0152535169ec3b42edde2951bdcd (diff) | |
download | mongo-367810995073e01ee58159deb1bb5b878882632f.tar.gz |
SERVER-16101 replace DeleteExecutor with ParsedDelete
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/batch_executor.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/exec/delete.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/exec/delete.h | 8 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/ops/delete.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/ops/delete_executor.cpp | 199 | ||||
-rw-r--r-- | src/mongo/db/ops/delete_executor.h | 132 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_delete.cpp | 104 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_delete.h | 109 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 152 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.h | 50 |
13 files changed, 385 insertions, 468 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index da637d789d1..a1979673f24 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -649,8 +649,8 @@ serverOnlyFiles = [ "db/background.cpp", "db/matcher/expression_where.cpp", "db/operation_context_impl.cpp", "db/ops/delete.cpp", - "db/ops/delete_executor.cpp", "db/ops/insert.cpp", + "db/ops/parsed_delete.cpp", "db/ops/parsed_update.cpp", "db/ops/update.cpp", "db/ops/update_lifecycle_impl.cpp", diff --git a/src/mongo/db/commands/write_commands/batch_executor.cpp b/src/mongo/db/commands/write_commands/batch_executor.cpp index a5801823f0d..cf356b848c3 100644 --- a/src/mongo/db/commands/write_commands/batch_executor.cpp +++ b/src/mongo/db/commands/write_commands/batch_executor.cpp @@ -46,9 +46,10 @@ #include "mongo/db/catalog/database_holder.h" #include "mongo/db/catalog/index_create.h" #include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/exec/delete.h" #include "mongo/db/exec/update.h" -#include "mongo/db/ops/delete_executor.h" #include "mongo/db/ops/delete_request.h" +#include "mongo/db/ops/parsed_delete.h" #include "mongo/db/ops/parsed_update.h" #include "mongo/db/ops/insert.h" #include "mongo/db/ops/update_lifecycle_impl.h" @@ -1345,9 +1346,9 @@ namespace mongo { while ( 1 ) { try { - DeleteExecutor executor( txn, &request ); - Status status = executor.prepare(); - if ( !status.isOK() ) { + ParsedDelete parsedDelete(txn, &request); + Status status = parsedDelete.parseRequest(); + if (!status.isOK()) { result->setError(toWriteError(status)); return; } @@ -1368,7 +1369,16 @@ namespace mongo { // TODO: better constructor? Client::Context ctx(txn, nss.ns(), false /* don't check version */); - result->getStats().n = executor.execute(autoDb.getDb()); + PlanExecutor* rawExec; + uassertStatusOK(getExecutorDelete(txn, + ctx.db()->getCollection(txn, nss), + &parsedDelete, + &rawExec)); + boost::scoped_ptr<PlanExecutor> exec(rawExec); + + // Execute the delete and retrieve the number deleted. + uassertStatusOK(exec->executePlan()); + result->getStats().n = DeleteStage::getNumDeleted(exec.get()); break; } diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index 2b7f523636f..b6a71b468cb 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -38,8 +38,8 @@ #include "mongo/db/curop.h" #include "mongo/db/json.h" #include "mongo/db/lasterror.h" -#include "mongo/db/ops/delete_executor.h" #include "mongo/db/ops/delete_request.h" +#include "mongo/db/ops/parsed_delete.h" #include "mongo/db/ops/parsed_update.h" #include "mongo/db/ops/update_lifecycle_impl.h" #include "mongo/db/query/explain.h" @@ -252,12 +252,10 @@ namespace mongo { // Explained deletes can yield. deleteRequest.setYieldPolicy(PlanExecutor::YIELD_AUTO); - // Use the request to create a DeleteExecutor, and from it extract the - // plan tree which will be used to execute this update. - DeleteExecutor deleteExecutor( txn, &deleteRequest ); - Status prepStatus = deleteExecutor.prepare(); - if ( !prepStatus.isOK() ) { - return prepStatus; + ParsedDelete parsedDelete(txn, &deleteRequest); + Status parseStatus = parsedDelete.parseRequest(); + if (!parseStatus.isOK()) { + return parseStatus; } // Explains of write commands are read-only, but we take write locks so that timing @@ -271,16 +269,18 @@ namespace mongo { // or collections. ensureShardVersionOKOrThrow( nsString.ns() ); - Status prepInLockStatus = deleteExecutor.prepareInLock( autoDb.getDb() ); - if ( !prepInLockStatus.isOK()) { - return prepInLockStatus; + // Get a pointer to the (possibly NULL) collection. + Collection* collection = NULL; + if (autoDb.getDb()) { + collection = autoDb.getDb()->getCollection(txn, nsString.ns()); } - // Executor registration and yield policy is handled internally by the delete executor. - PlanExecutor* exec = deleteExecutor.getPlanExecutor(); + PlanExecutor* rawExec; + uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete, &rawExec)); + boost::scoped_ptr<PlanExecutor> exec(rawExec); // Explain the plan tree. - Explain::explainStages( exec, verbosity, out ); + Explain::explainStages(exec.get(), verbosity, out); return Status::OK(); } } diff --git a/src/mongo/db/exec/delete.cpp b/src/mongo/db/exec/delete.cpp index 6d9e2829cbf..1765a698a60 100644 --- a/src/mongo/db/exec/delete.cpp +++ b/src/mongo/db/exec/delete.cpp @@ -204,4 +204,14 @@ namespace mongo { return &_specificStats; } + // static + long long DeleteStage::getNumDeleted(PlanExecutor* exec) { + invariant(exec->getRootStage()->isEOF()); + invariant(exec->getRootStage()->stageType() == STAGE_DELETE); + DeleteStage* deleteStage = static_cast<DeleteStage*>(exec->getRootStage()); + const DeleteStats* deleteStats = + static_cast<const DeleteStats*>(deleteStage->getSpecificStats()); + return deleteStats->docsDeleted; + } + } // namespace mongo diff --git a/src/mongo/db/exec/delete.h b/src/mongo/db/exec/delete.h index aa7512eb1e8..4c64679fde9 100644 --- a/src/mongo/db/exec/delete.h +++ b/src/mongo/db/exec/delete.h @@ -34,6 +34,7 @@ namespace mongo { class OperationContext; + class PlanExecutor; struct DeleteStageParams { DeleteStageParams() : @@ -93,6 +94,13 @@ namespace mongo { static const char* kStageType; + /** + * Extracts the number of documents deleted by the update plan 'exec'. + * + * Should only be called if the root plan stage of 'exec' is UPDATE and if 'exec' is EOF. + */ + static long long getNumDeleted(PlanExecutor* exec); + private: // Transactional context. Not owned by us. OperationContext* _txn; diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 64a8e14451a..74ebd47def0 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -64,10 +64,11 @@ #include "mongo/db/mongod_options.h" #include "mongo/db/namespace_string.h" #include "mongo/db/catalog/index_create.h" +#include "mongo/db/exec/delete.h" #include "mongo/db/exec/update.h" -#include "mongo/db/ops/delete_executor.h" #include "mongo/db/ops/delete_request.h" #include "mongo/db/ops/insert.h" +#include "mongo/db/ops/parsed_delete.h" #include "mongo/db/ops/parsed_update.h" #include "mongo/db/ops/update_lifecycle_impl.h" #include "mongo/db/ops/update_driver.h" @@ -681,8 +682,8 @@ namespace { int attempt = 1; while ( 1 ) { try { - DeleteExecutor executor(txn, &request); - uassertStatusOK(executor.prepare()); + ParsedDelete parsedDelete(txn, &request); + uassertStatusOK(parsedDelete.parseRequest()); AutoGetDb autoDb(txn, ns.db(), MODE_IX); if (!autoDb.getDb()) break; @@ -690,8 +691,17 @@ namespace { Lock::CollectionLock colLock(txn->lockState(), ns.ns(), MODE_IX); Client::Context ctx(txn, ns); - long long n = executor.execute(ctx.db()); - lastError.getSafe()->recordDelete( n ); + PlanExecutor* rawExec; + uassertStatusOK(getExecutorDelete(txn, + ctx.db()->getCollection(txn, ns), + &parsedDelete, + &rawExec)); + boost::scoped_ptr<PlanExecutor> exec(rawExec); + + // Run the plan and get the number of docs deleted. + uassertStatusOK(exec->executePlan()); + long long n = DeleteStage::getNumDeleted(exec.get()); + lastError.getSafe()->recordDelete(n); op.debug().ndeleted = n; break; diff --git a/src/mongo/db/ops/delete.cpp b/src/mongo/db/ops/delete.cpp index 8fc12448a21..fcaa2b6d02b 100644 --- a/src/mongo/db/ops/delete.cpp +++ b/src/mongo/db/ops/delete.cpp @@ -28,8 +28,10 @@ #include "mongo/db/ops/delete.h" -#include "mongo/db/ops/delete_executor.h" +#include "mongo/db/exec/delete.h" #include "mongo/db/ops/delete_request.h" +#include "mongo/db/ops/parsed_delete.h" +#include "mongo/db/query/get_executor.h" namespace mongo { @@ -55,8 +57,21 @@ namespace mongo { request.setGod(god); request.setFromMigrate(fromMigrate); request.setYieldPolicy(policy); - DeleteExecutor executor(txn, &request); - return executor.execute(db); + + Collection* collection = NULL; + if (db) { + collection = db->getCollection(txn, nsString.ns()); + } + + ParsedDelete parsedDelete(txn, &request); + uassertStatusOK(parsedDelete.parseRequest()); + + PlanExecutor* rawExec; + uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete, &rawExec)); + boost::scoped_ptr<PlanExecutor> exec(rawExec); + + uassertStatusOK(exec->executePlan()); + return DeleteStage::getNumDeleted(exec.get()); } } // namespace mongo diff --git a/src/mongo/db/ops/delete_executor.cpp b/src/mongo/db/ops/delete_executor.cpp deleted file mode 100644 index 01b08e6c56d..00000000000 --- a/src/mongo/db/ops/delete_executor.cpp +++ /dev/null @@ -1,199 +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/delete_executor.h" - -#include "mongo/db/catalog/collection.h" -#include "mongo/db/catalog/database.h" -#include "mongo/db/exec/delete.h" -#include "mongo/db/ops/delete_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/repl_coordinator_global.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { - - DeleteExecutor::DeleteExecutor(OperationContext* txn, const DeleteRequest* request) : - _txn(txn), - _request(request), - _canonicalQuery(), - _isQueryParsed(false) { - } - - DeleteExecutor::~DeleteExecutor() {} - - Status DeleteExecutor::prepare() { - if (_isQueryParsed) - return Status::OK(); - - dassert(!_canonicalQuery.get()); - - if (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()) { - _canonicalQuery.reset(cqRaw); - _isQueryParsed = true; - } - - return status; - } - - PlanExecutor* DeleteExecutor::getPlanExecutor() { - return _exec.get(); - } - - Status DeleteExecutor::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(); - } - - uassert(17417, - mongoutils::str::stream() << - "DeleteExecutor::prepare() failed to parse query " << _request->getQuery(), - _isQueryParsed); - - const NamespaceString& ns(_request->getNamespaceString()); - if (!_request->isGod()) { - if (ns.isSystem()) { - uassert(12050, - "cannot delete from system namespace", - legalClientSystemNS(ns.ns(), true)); - } - if (ns.ns().find('$') != string::npos) { - log() << "cannot delete from collection with reserved $ in name: " << ns << endl; - uasserted(10100, "cannot delete from collection with reserved $ in name"); - } - } - - // Note that 'collection' may by NULL in the case that the collection or database we are - // trying to delete from does not exist. NULL 'collection' is handled by - // getExecutorDelete(); we expect to get back a plan executor whose plan is a DeleteStage - // on top of an EOFStage. - Collection* collection = NULL; - if (db) { - collection = db->getCollection(_txn, ns.ns()); - } - - if (collection && collection->isCapped()) { - return Status(ErrorCodes::IllegalOperation, - str::stream() << "cannot remove from a capped collection: " << ns.ns()); - } - - if (_request->shouldCallLogOp() && - !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(ns.db())) { - return Status(ErrorCodes::NotMaster, - str::stream() << "Not primary while removing from " << ns.ns()); - } - - // 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; - Status getExecStatus = Status::OK(); - if (_canonicalQuery.get()) { - // This is the non-idhack branch. - getExecStatus = getExecutorDelete(_txn, - collection, - _canonicalQuery.release(), - _request->isMulti(), - _request->shouldCallLogOp(), - _request->isFromMigrate(), - _request->isExplain(), - policy, - &rawExec); - } - else { - // This is the idhack branch. - getExecStatus = getExecutorDelete(_txn, - collection, - ns.ns(), - _request->getQuery(), - _request->isMulti(), - _request->shouldCallLogOp(), - _request->isFromMigrate(), - _request->isExplain(), - policy, - &rawExec); - } - - if (!getExecStatus.isOK()) { - return getExecStatus; - } - - invariant(rawExec); - _exec.reset(rawExec); - - return Status::OK(); - } - - long long DeleteExecutor::execute(Database* db) { - uassertStatusOK(prepare()); - - // If we've already done the in-lock preparation, this is a no-op. - uassertStatusOK(prepareInLock(db)); - invariant(_exec.get()); - - uassertStatusOK(_exec->executePlan()); - - // Extract the number of documents deleted from the DeleteStage stats. - invariant(_exec->getRootStage()->stageType() == STAGE_DELETE); - DeleteStage* deleteStage = static_cast<DeleteStage*>(_exec->getRootStage()); - const DeleteStats* deleteStats = - static_cast<const DeleteStats*>(deleteStage->getSpecificStats()); - return deleteStats->docsDeleted; - } - -} // namespace mongo diff --git a/src/mongo/db/ops/delete_executor.h b/src/mongo/db/ops/delete_executor.h deleted file mode 100644 index cc984af1c95..00000000000 --- a/src/mongo/db/ops/delete_executor.h +++ /dev/null @@ -1,132 +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/query/plan_executor.h" - - -namespace mongo { - - class CanonicalQuery; - class Database; - class DeleteRequest; - class OperationContext; - - /** - * Implementation of the processing of a delete 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 delete, but the caller must already hold the - * appropriate database lock. - * - * Expected usage is approximately: - * DeleteRequest request(...); - * // configure request - * DeleteExecutor executor(txn, &request); - * uassertStatusOK(executor.prepare()); - * // Get locks, get ready to execute. - * try { - * long long nDeleted = executor.execute(); - * } - * catch (const DBException& ex) { - * // Error handling. - * } - */ - class DeleteExecutor { - MONGO_DISALLOW_COPYING(DeleteExecutor); - public: - /** - * Constructs a delete executor. - * - * The object pointed to by "request" must stay in scope for the life of the constructed - * executor. - */ - DeleteExecutor(OperationContext* txn, const DeleteRequest* request); - - ~DeleteExecutor(); - - /** - * 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 delete 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 delete upon calling - * execute(). Returns NULL if no PlanExecutor has been created. - */ - PlanExecutor* getPlanExecutor(); - - /** - * Execute a delete. Requires the caller to hold the database lock on the - * appropriate resources for the request. - * - * Returns the number of documents deleted. - */ - long long execute(Database* db); - - private: - // Transactional context. Not owned by us. - OperationContext* _txn; - - // Unowned pointer to the request object that this executor will process. - const DeleteRequest* const _request; - - // 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; - - }; - -} // namespace mongo diff --git a/src/mongo/db/ops/parsed_delete.cpp b/src/mongo/db/ops/parsed_delete.cpp new file mode 100644 index 00000000000..da1b2c77a25 --- /dev/null +++ b/src/mongo/db/ops/parsed_delete.cpp @@ -0,0 +1,104 @@ +/** + * 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/parsed_delete.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/database.h" +#include "mongo/db/exec/delete.h" +#include "mongo/db/ops/delete_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/repl_coordinator_global.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + ParsedDelete::ParsedDelete(OperationContext* txn, const DeleteRequest* request) : + _txn(txn), + _request(request) { } + + Status ParsedDelete::parseRequest() { + dassert(!_canonicalQuery.get()); + + if (CanonicalQuery::isSimpleIdQuery(_request->getQuery())) { + return Status::OK(); + } + + return parseQueryToCQ(); + } + + Status ParsedDelete::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; + } + + const DeleteRequest* ParsedDelete::getRequest() const { + return _request; + } + + bool ParsedDelete::canYield() const { + return !_request->isGod() && + PlanExecutor::YIELD_AUTO == _request->getYieldPolicy() && ( + _canonicalQuery.get() ? + !QueryPlannerCommon::hasNode(_canonicalQuery->root(), MatchExpression::ATOMIC) : + !LiteParsedQuery::isQueryIsolated(_request->getQuery())); + } + + bool ParsedDelete::hasParsedQuery() const { + return _canonicalQuery.get() != NULL; + } + + CanonicalQuery* ParsedDelete::releaseParsedQuery() { + invariant(_canonicalQuery.get() != NULL); + return _canonicalQuery.release(); + } + +} // namespace mongo diff --git a/src/mongo/db/ops/parsed_delete.h b/src/mongo/db/ops/parsed_delete.h new file mode 100644 index 00000000000..c117db28cb7 --- /dev/null +++ b/src/mongo/db/ops/parsed_delete.h @@ -0,0 +1,109 @@ +/** + * 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/query/plan_executor.h" + +namespace mongo { + + class CanonicalQuery; + class Database; + class DeleteRequest; + class OperationContext; + + /** + * This class takes a pointer to a DeleteRequest, and converts that request into a parsed form + * via the parseRequest() method. A ParsedDelete can then be used to retrieve a PlanExecutor + * capable of executing the delete. + * + * A delete request is parsed to a CanonicalQuery, so this class is a thin, delete-specific + * wrapper around canonicalization. + * + * No locks need to be held during parsing. + */ + class ParsedDelete { + MONGO_DISALLOW_COPYING(ParsedDelete); + public: + /** + * Constructs a parsed delete. + * + * The object pointed to by "request" must stay in scope for the life of the constructed + * ParsedDelete. + */ + ParsedDelete(OperationContext* txn, const DeleteRequest* request); + + /** + * Parses the delete request to a canonical query. On success, the parsed delete can be + * used to create a PlanExecutor capable of executing this delete. + */ + 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 DeleteRequest* getRequest() const; + + /** + * Is this delete 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: + // Transactional context. Not owned by us. + OperationContext* _txn; + + // Unowned pointer to the request object that this executor will process. + const DeleteRequest* const _request; + + // 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/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 290ec20adf4..cdf665fc0b1 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -460,87 +460,101 @@ namespace mongo { Status getExecutorDelete(OperationContext* txn, Collection* collection, - CanonicalQuery* rawCanonicalQuery, - bool isMulti, - bool shouldCallLogOp, - bool fromMigrate, - bool isExplain, - PlanExecutor::YieldPolicy yieldPolicy, - PlanExecutor** out) { - auto_ptr<CanonicalQuery> canonicalQuery(rawCanonicalQuery); + ParsedDelete* parsedDelete, + PlanExecutor** execOut) { + const DeleteRequest* request = parsedDelete->getRequest(); + + const NamespaceString& nss(request->getNamespaceString()); + if (!request->isGod()) { + if (nss.isSystem()) { + uassert(12050, + "cannot delete from system namespace", + legalClientSystemNS(nss.ns(), true)); + } + if (nss.ns().find('$') != string::npos) { + log() << "cannot delete from collection with reserved $ in name: " << nss << endl; + uasserted(10100, "cannot delete from collection with reserved $ in name"); + } + } + + if (collection && collection->isCapped()) { + return Status(ErrorCodes::IllegalOperation, + str::stream() << "cannot remove from a capped collection: " << nss.ns()); + } + + if (request->shouldCallLogOp() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nss.db())) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while removing from " << nss.ns()); + } + + DeleteStageParams deleteStageParams; + deleteStageParams.isMulti = request->isMulti(); + deleteStageParams.shouldCallLogOp = request->shouldCallLogOp(); + deleteStageParams.fromMigrate = request->isFromMigrate(); + deleteStageParams.isExplain = request->isExplain(); + auto_ptr<WorkingSet> ws(new WorkingSet()); + PlanExecutor::YieldPolicy policy = parsedDelete->canYield() ? PlanExecutor::YIELD_AUTO : + PlanExecutor::YIELD_MANUAL; + + if (!parsedDelete->hasParsedQuery()) { + // This is the idhack fast-path for getting a PlanExecutor without doing the work + // to create a CanonicalQuery. + const BSONObj& unparsedQuery = request->getQuery(); + + if (!collection) { + // Treat collections that do not exist as empty collections. Note that the explain + // reporting machinery always assumes that the root stage for a delete operation is + // a DeleteStage, so in this case we put a DeleteStage on top of an EOFStage. + LOG(2) << "Collection " << nss.ns() << " does not exist." + << " Using EOF stage: " << unparsedQuery.toString(); + DeleteStage* deleteStage = new DeleteStage(txn, deleteStageParams, ws.get(), NULL, + new EOFStage()); + return PlanExecutor::make(txn, ws.release(), deleteStage, nss.ns(), policy, + execOut); + + } + + if (CanonicalQuery::isSimpleIdQuery(unparsedQuery) && + collection->getIndexCatalog()->findIdIndex(txn)) { + LOG(2) << "Using idhack: " << unparsedQuery.toString(); + + PlanStage* idHackStage = new IDHackStage(txn, + collection, + unparsedQuery["_id"].wrap(), + ws.get()); + DeleteStage* root = new DeleteStage(txn, deleteStageParams, ws.get(), collection, + idHackStage); + return PlanExecutor::make(txn, ws.release(), root, collection, policy, execOut); + } + + // If we're here then we don't have a parsed query, but we're also not eligible for + // the idhack fast path. We need to force canonicalization now. + Status cqStatus = parsedDelete->parseQueryToCQ(); + if (!cqStatus.isOK()) { + return cqStatus; + } + } + + // This is the regular path for when we have a CanonicalQuery. + std::auto_ptr<CanonicalQuery> cq(parsedDelete->releaseParsedQuery()); + PlanStage* root; QuerySolution* querySolution; const size_t defaultPlannerOptions = 0; - Status status = prepareExecution(txn, collection, ws.get(), canonicalQuery.get(), + Status status = prepareExecution(txn, collection, ws.get(), cq.get(), defaultPlannerOptions, &root, &querySolution); if (!status.isOK()) { return status; } invariant(root); - DeleteStageParams deleteStageParams; - deleteStageParams.isMulti = isMulti; - deleteStageParams.shouldCallLogOp = shouldCallLogOp; - deleteStageParams.fromMigrate = fromMigrate; - deleteStageParams.isExplain = isExplain; + root = new DeleteStage(txn, deleteStageParams, ws.get(), collection, root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be null. - return PlanExecutor::make(txn, ws.release(), root, querySolution, canonicalQuery.release(), - collection, yieldPolicy, out); - } - - Status getExecutorDelete(OperationContext* txn, - Collection* collection, - const std::string& ns, - const BSONObj& unparsedQuery, - bool isMulti, - bool shouldCallLogOp, - bool fromMigrate, - bool isExplain, - PlanExecutor::YieldPolicy yieldPolicy, - PlanExecutor** out) { - auto_ptr<WorkingSet> ws(new WorkingSet()); - DeleteStageParams deleteStageParams; - deleteStageParams.isMulti = isMulti; - deleteStageParams.shouldCallLogOp = shouldCallLogOp; - deleteStageParams.fromMigrate = fromMigrate; - deleteStageParams.isExplain = isExplain; - if (!collection) { - // Treat collections that do not exist as empty collections. Note that the explain - // reporting machinery always assumes that the root stage for a delete operation is a - // DeleteStage, so in this case we put a DeleteStage on top of an EOFStage. - LOG(2) << "Collection " << ns << " does not exist." - << " Using EOF stage: " << unparsedQuery.toString(); - DeleteStage* deleteStage = new DeleteStage(txn, deleteStageParams, ws.get(), NULL, - new EOFStage()); - return PlanExecutor::make(txn, ws.release(), deleteStage, ns, yieldPolicy, out); - } - - if (CanonicalQuery::isSimpleIdQuery(unparsedQuery) && - collection->getIndexCatalog()->findIdIndex(txn)) { - LOG(2) << "Using idhack: " << unparsedQuery.toString(); - - PlanStage* idHackStage = new IDHackStage(txn, collection, unparsedQuery["_id"].wrap(), - ws.get()); - DeleteStage* root = new DeleteStage(txn, deleteStageParams, ws.get(), collection, - idHackStage); - return PlanExecutor::make(txn, ws.release(), root, collection, yieldPolicy, out); - } - - const WhereCallbackReal whereCallback(txn, collection->ns().db()); - CanonicalQuery* cq; - Status status = CanonicalQuery::canonicalize(collection->ns(), - unparsedQuery, - deleteStageParams.isExplain, - &cq, - whereCallback); - if (!status.isOK()) - return status; - - // Takes ownership of 'cq'. - return getExecutorDelete(txn, collection, cq, isMulti, shouldCallLogOp, - fromMigrate, isExplain, yieldPolicy, out); + return PlanExecutor::make(txn, ws.release(), root, querySolution, cq.release(), + collection, policy, execOut); } // diff --git a/src/mongo/db/query/get_executor.h b/src/mongo/db/query/get_executor.h index ab100a9e00e..f298851c3cf 100644 --- a/src/mongo/db/query/get_executor.h +++ b/src/mongo/db/query/get_executor.h @@ -31,6 +31,8 @@ #include "mongo/db/query/query_planner_params.h" #include "mongo/db/query/query_settings.h" #include "mongo/db/query/query_solution.h" +#include "mongo/db/ops/delete_request.h" +#include "mongo/db/ops/parsed_delete.h" #include "mongo/db/ops/parsed_update.h" #include "mongo/db/ops/update_driver.h" #include "mongo/db/ops/update_request.h" @@ -131,55 +133,25 @@ namespace mongo { PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** execOut); - // - // Delete - // - /** - * Get a PlanExecutor for a delete operation. 'rawCanonicalQuery' describes the predicate for - * the documents to be deleted. A write lock is required to execute the returned plan. - * - * Takes ownership of 'rawCanonicalQuery'. + * Get a PlanExecutor for a delete operation. 'parsedDelete' describes the query predicate + * and delete flags like 'isMulti'. The caller must hold the appropriate MODE_X or MODE_IX + * locks, and must not release these locks until after the returned PlanExecutor is deleted. * - * If the query is valid and an executor could be created, returns Status::OK() and populates - * *out with the PlanExecutor. + * The returned PlanExecutor will yield if and only if parsedDelete->canYield(). * - * If the query cannot be executed, returns a Status indicating why. - */ - Status getExecutorDelete(OperationContext* txn, - Collection* collection, - CanonicalQuery* rawCanonicalQuery, - bool isMulti, - bool shouldCallLogOp, - bool fromMigrate, - bool isExplain, - PlanExecutor::YieldPolicy yieldPolicy, - PlanExecutor** execOut); - - /** - * Overload of getExecutorDelete() above, for when a canonicalQuery is not available. Used to - * support idhack-powered deletes. + * Does not take ownership of its arguments. * * If the query is valid and an executor could be created, returns Status::OK() and populates - * *out with the PlanExecutor. + * *execOut with the PlanExecutor. The caller takes ownership of *execOut. * * If the query cannot be executed, returns a Status indicating why. */ Status getExecutorDelete(OperationContext* txn, Collection* collection, - const std::string& ns, - const BSONObj& unparsedQuery, - bool isMulti, - bool shouldCallLogOp, - bool fromMigrate, - bool isExplain, - PlanExecutor::YieldPolicy yieldPolicy, + ParsedDelete* parsedDelete, PlanExecutor** execOut); - // - // Update - // - /** * Get a PlanExecutor for an update operation. 'parsedUpdate' describes the query predicate * and update modifiers. The caller must hold the appropriate MODE_X or MODE_IX locks prior @@ -201,10 +173,6 @@ namespace mongo { OpDebug* opDebug, PlanExecutor** execOut); - // - // Group - // - /** * Get a PlanExecutor for a group operation. 'rawCanonicalQuery' describes the predicate for * the documents to be grouped. |