/* ns: namespace, e.g. <database>.<collection> pattern: the "where" clause / criteria justOne: stop after 1 match god: allow access to system namespaces, and don't yield */ long long deleteObjects(OperationContext* txn, Database* db, StringData ns, BSONObj pattern, PlanExecutor::YieldPolicy policy, bool justOne, bool god, bool fromMigrate) { NamespaceString nsString(ns); DeleteRequest request(nsString); request.setQuery(pattern); request.setMulti(!justOne); request.setGod(god); request.setFromMigrate(fromMigrate); request.setYieldPolicy(policy); Collection* collection = NULL; if (db) { collection = db->getCollection(nsString.ns()); } ParsedDelete parsedDelete(txn, &request); uassertStatusOK(parsedDelete.parseRequest()); // Replicated writes are disallowed with deleteObjects, as we are not properly setting // lastOp for no-op deletes. fassert(22001, !txn->writesAreReplicated()); std::unique_ptr<PlanExecutor> exec = uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete)); uassertStatusOK(exec->executePlan()); return DeleteStage::getNumDeleted(*exec); }
Status WriteCmd::explain(OperationContext* txn, const std::string& dbname, const BSONObj& cmdObj, ExplainCommon::Verbosity verbosity, BSONObjBuilder* out) const { // For now we only explain update and delete write commands. if ( BatchedCommandRequest::BatchType_Update != _writeType && BatchedCommandRequest::BatchType_Delete != _writeType ) { return Status( ErrorCodes::IllegalOperation, "Only update and delete write ops can be explained" ); } // Parse the batch request. BatchedCommandRequest request( _writeType ); std::string errMsg; if ( !request.parseBSON( cmdObj, &errMsg ) || !request.isValid( &errMsg ) ) { return Status( ErrorCodes::FailedToParse, errMsg ); } // Note that this is a runCommmand, and therefore, the database and the collection name // are in different parts of the grammar for the command. But it's more convenient to // work with a NamespaceString. We built it here and replace it in the parsed command. // Internally, everything work with the namespace string as opposed to just the // collection name. NamespaceString nsString(dbname, request.getNS()); request.setNSS(nsString); // Do the validation of the batch that is shared with non-explained write batches. Status isValid = WriteBatchExecutor::validateBatch( request ); if (!isValid.isOK()) { return isValid; } // Explain must do one additional piece of validation: For now we only explain // singleton batches. if ( request.sizeWriteOps() != 1u ) { return Status( ErrorCodes::InvalidLength, "explained write batches must be of size 1" ); } // Get a reference to the singleton batch item (it's the 0th item in the batch). BatchItemRef batchItem( &request, 0 ); if ( BatchedCommandRequest::BatchType_Update == _writeType ) { // Create the update request. UpdateRequest updateRequest( nsString ); updateRequest.setQuery( batchItem.getUpdate()->getQuery() ); updateRequest.setUpdates( batchItem.getUpdate()->getUpdateExpr() ); updateRequest.setMulti( batchItem.getUpdate()->getMulti() ); updateRequest.setUpsert( batchItem.getUpdate()->getUpsert() ); updateRequest.setUpdateOpLog( true ); UpdateLifecycleImpl updateLifecycle( true, updateRequest.getNamespaceString() ); updateRequest.setLifecycle( &updateLifecycle ); updateRequest.setExplain(); // Explained updates can yield. updateRequest.setYieldPolicy(PlanExecutor::YIELD_AUTO); OpDebug* debug = &txn->getCurOp()->debug(); ParsedUpdate parsedUpdate( txn, &updateRequest ); Status parseStatus = parsedUpdate.parseRequest(); if ( !parseStatus.isOK() ) { return parseStatus; } // Explains of write commands are read-only, but we take write locks so // that timing info is more accurate. AutoGetDb autoDb( txn, nsString.db(), MODE_IX ); Lock::CollectionLock colLock( txn->lockState(), nsString.ns(), MODE_IX ); // We check the shard version explicitly here rather than using Client::Context, // as Context can do implicit database creation if the db does not exist. We want // explain to be a no-op that reports a trivial EOF plan against non-existent dbs // or collections. ensureShardVersionOKOrThrow( nsString.ns() ); // Get a pointer to the (possibly NULL) collection. Collection* collection = NULL; if ( autoDb.getDb() ) { collection = autoDb.getDb()->getCollection( txn, nsString.ns() ); } PlanExecutor* rawExec; uassertStatusOK(getExecutorUpdate(txn, collection, &parsedUpdate, debug, &rawExec)); boost::scoped_ptr<PlanExecutor> exec(rawExec); // Explain the plan tree. Explain::explainStages( exec.get(), verbosity, out ); return Status::OK(); } else { invariant( BatchedCommandRequest::BatchType_Delete == _writeType ); // Create the delete request. DeleteRequest deleteRequest( nsString ); deleteRequest.setQuery( batchItem.getDelete()->getQuery() ); deleteRequest.setMulti( batchItem.getDelete()->getLimit() != 1 ); deleteRequest.setUpdateOpLog(true); deleteRequest.setGod( false ); deleteRequest.setExplain(); // Explained deletes can yield. deleteRequest.setYieldPolicy(PlanExecutor::YIELD_AUTO); 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 // info is more accurate. AutoGetDb autoDb(txn, nsString.db(), MODE_IX); Lock::CollectionLock colLock(txn->lockState(), nsString.ns(), MODE_IX); // We check the shard version explicitly here rather than using Client::Context, // as Context can do implicit database creation if the db does not exist. We want // explain to be a no-op that reports a trivial EOF plan against non-existent dbs // or collections. ensureShardVersionOKOrThrow( nsString.ns() ); // Get a pointer to the (possibly NULL) collection. Collection* collection = NULL; if (autoDb.getDb()) { collection = autoDb.getDb()->getCollection(txn, nsString.ns()); } PlanExecutor* rawExec; uassertStatusOK(getExecutorDelete(txn, collection, &parsedDelete, &rawExec)); boost::scoped_ptr<PlanExecutor> exec(rawExec); // Explain the plan tree. Explain::explainStages(exec.get(), verbosity, out); return Status::OK(); } }
long long DeleteExecutor::execute(Database* db) { uassertStatusOK(prepare()); 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"); } } Collection* collection = db->getCollection(_request->getOpCtx(), ns.ns()); if (NULL == collection) { return 0; } uassert(10101, str::stream() << "cannot remove from a capped collection: " << ns.ns(), !collection->isCapped()); uassert(ErrorCodes::NotMaster, str::stream() << "Not primary while removing from " << ns.ns(), !_request->shouldCallLogOp() || repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(ns.db())); PlanExecutor* rawExec; if (_canonicalQuery.get()) { // This is the non-idhack branch. uassertStatusOK(getExecutorDelete(_request->getOpCtx(), collection, _canonicalQuery.release(), _request->isMulti(), _request->shouldCallLogOp(), &rawExec)); } else { // This is the idhack branch. uassertStatusOK(getExecutorDelete(_request->getOpCtx(), collection, ns.ns(), _request->getQuery(), _request->isMulti(), _request->shouldCallLogOp(), &rawExec)); } scoped_ptr<PlanExecutor> exec(rawExec); // Concurrently mutating state (by us) so we need to register 'exec'. const ScopedExecutorRegistration safety(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; }
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 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 = db->getCollection(_request->getOpCtx(), 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(_request->getOpCtx(), collection, _canonicalQuery.release(), _request->isMulti(), _request->shouldCallLogOp(), _request->isFromMigrate(), _request->isExplain(), policy, &rawExec); } else { // This is the idhack branch. getExecStatus = getExecutorDelete(_request->getOpCtx(), 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(); }