Esempio n. 1
0
/* 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);
}
Esempio n. 2
0
    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;
    }
Esempio n. 4
0
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();
}