Exemplo n.º 1
0
    bool WriteBatchExecutor::doUpdate( const string& ns,
                                       const BatchedUpdateDocument& updateOp,
                                       CurOp* currentOp,
                                       WriteStats* stats,
                                       BSONObj* upsertedID,
                                       BatchedErrorDetail* error ) {
        OpDebug& opDebug = currentOp->debug();

        _opCounters->gotUpdate();

        BSONObj queryObj = updateOp.getQuery();
        BSONObj updateObj = updateOp.getUpdateExpr();
        bool multi = updateOp.isMultiSet() ? updateOp.getMulti() : false;
        bool upsert = updateOp.isUpsertSet() ? updateOp.getUpsert() : false;

        currentOp->setQuery( queryObj );
        opDebug.op = dbUpdate;
        opDebug.query = queryObj;

        bool updateExisting = false;
        long long numUpdated = 0;
        BSONObj resUpsertedID;
        try {

            const NamespaceString requestNs( ns );
            UpdateRequest request( requestNs );

            request.setQuery( queryObj );
            request.setUpdates( updateObj );
            request.setUpsert( upsert );
            request.setMulti( multi );
            request.setUpdateOpLog();

            UpdateResult res = update( request, &opDebug );

            updateExisting = res.existing;
            numUpdated = res.numMatched;
            resUpsertedID = res.upserted;

            stats->numUpdated += resUpsertedID.isEmpty() ? numUpdated : 0;
            stats->numUpserted += !resUpsertedID.isEmpty() ? 1 : 0;
        }
        catch ( const UserException& ex ) {
            opDebug.exceptionInfo = ex.getInfo();
            toBatchedError( ex, error );
            return false;
        }

        _le->recordUpdate( updateExisting, numUpdated, resUpsertedID );

        if (!resUpsertedID.isEmpty()) {
            *upsertedID = resUpsertedID;
        }

        return true;
    }
    Status ChunkManagerTargeter::targetUpdate( const BatchedUpdateDocument& updateDoc,
                                               vector<ShardEndpoint*>* endpoints ) const {

        //
        // Update targeting may use either the query or the update.  This is to support save-style
        // updates, of the form:
        //
        // coll.update({ _id : xxx }, { _id : xxx, shardKey : 1, foo : bar }, { upsert : true })
        //
        // Because drivers do not know the shard key, they can't pull the shard key automatically
        // into the query doc, and to correctly support upsert we must target a single shard.
        //
        // The rule is simple - If the update is replacement style (no '$set'), we target using the
        // update.  If the update is replacement style, we target using the query.
        //

        BSONObj query = updateDoc.getQuery();
        BSONObj updateExpr = updateDoc.getUpdateExpr();

        UpdateType updateType = getUpdateExprType( updateDoc.getUpdateExpr() );

        if ( updateType == UpdateType_Unknown ) {
            return Status( ErrorCodes::UnsupportedFormat,
                           stream() << "update document " << updateExpr
                                    << " has mixed $operator and non-$operator style fields" );
        }

        BSONObj targetedDoc = updateType == UpdateType_OpStyle ? query : updateExpr;

        bool exactShardKeyQuery = false;

        if ( _manager ) {

            //
            // Sharded collections have the following futher requirements for targeting:
            //
            // Upserts must be targeted exactly by shard key.
            // Non-multi updates must be targeted exactly by shard key *or* exact _id.
            //

            exactShardKeyQuery = _manager->hasTargetableShardKey(targetedDoc);

            if ( updateDoc.getUpsert() && !exactShardKeyQuery ) {
                return Status( ErrorCodes::ShardKeyNotFound,
                               stream() << "upsert " << updateDoc.toBSON()
                                        << " does not contain shard key for pattern "
                                        << _manager->getShardKey().key() );
            }

            bool exactIdQuery = isExactIdQuery( updateDoc.getQuery() );

            if ( !updateDoc.getMulti() && !exactShardKeyQuery && !exactIdQuery ) {
                return Status( ErrorCodes::ShardKeyNotFound,
                               stream() << "update " << updateDoc.toBSON()
                                        << " does not contain _id or shard key for pattern "
                                        << _manager->getShardKey().key() );
            }

            // Track autosplit stats for sharded collections
            // Note: this is only best effort accounting and is not accurate.
            if ( exactShardKeyQuery ) {
                ChunkPtr chunk = _manager->findChunkForDoc(targetedDoc);
                _stats->chunkSizeDelta[chunk->getMin()] +=
                    ( query.objsize() + updateExpr.objsize() );
            }
        }

        Status result = Status::OK();
        if (exactShardKeyQuery) {
            // We can't rely on our query targeting to be exact
            ShardEndpoint* endpoint = NULL;
            result = targetShardKey(targetedDoc, &endpoint);
            endpoints->push_back(endpoint);

            invariant(result.isOK());
            invariant(NULL != endpoint);
        }
        else {
            result = targetQuery(targetedDoc, endpoints);
        }

        return result;
    }
Status ChunkManagerTargeter::targetUpdate(OperationContext* txn,
                                          const BatchedUpdateDocument& updateDoc,
                                          vector<ShardEndpoint*>* endpoints) const {
    //
    // Update targeting may use either the query or the update.  This is to support save-style
    // updates, of the form:
    //
    // coll.update({ _id : xxx }, { _id : xxx, shardKey : 1, foo : bar }, { upsert : true })
    //
    // Because drivers do not know the shard key, they can't pull the shard key automatically
    // into the query doc, and to correctly support upsert we must target a single shard.
    //
    // The rule is simple - If the update is replacement style (no '$set'), we target using the
    // update.  If the update is replacement style, we target using the query.
    //
    // If we have the exact shard key in either the query or replacement doc, we target using
    // that extracted key.
    //

    BSONObj query = updateDoc.getQuery();
    BSONObj updateExpr = updateDoc.getUpdateExpr();

    UpdateType updateType = getUpdateExprType(updateDoc.getUpdateExpr());

    if (updateType == UpdateType_Unknown) {
        return Status(ErrorCodes::UnsupportedFormat,
                      stream() << "update document " << updateExpr
                               << " has mixed $operator and non-$operator style fields");
    }

    BSONObj shardKey;

    if (_manager) {
        //
        // Sharded collections have the following futher requirements for targeting:
        //
        // Upserts must be targeted exactly by shard key.
        // Non-multi updates must be targeted exactly by shard key *or* exact _id.
        //

        // Get the shard key
        if (updateType == UpdateType_OpStyle) {
            // Target using the query
            StatusWith<BSONObj> status =
                _manager->getShardKeyPattern().extractShardKeyFromQuery(query);

            // Bad query
            if (!status.isOK())
                return status.getStatus();

            shardKey = status.getValue();
        } else {
            // Target using the replacement document
            shardKey = _manager->getShardKeyPattern().extractShardKeyFromDoc(updateExpr);
        }

        //
        // Extra sharded update validation
        //

        if (updateDoc.getUpsert()) {
            // Sharded upserts *always* need to be exactly targeted by shard key
            if (shardKey.isEmpty()) {
                return Status(ErrorCodes::ShardKeyNotFound,
                              stream() << "upsert " << updateDoc.toBSON()
                                       << " does not contain shard key for pattern "
                                       << _manager->getShardKeyPattern().toString());
            }

            // Also check shard key size on upsert
            Status status = ShardKeyPattern::checkShardKeySize(shardKey);
            if (!status.isOK())
                return status;
        }

        // Validate that single (non-multi) sharded updates are targeted by shard key or _id
        if (!updateDoc.getMulti() && shardKey.isEmpty() && !isExactIdQuery(updateDoc.getQuery())) {
            return Status(ErrorCodes::ShardKeyNotFound,
                          stream() << "update " << updateDoc.toBSON()
                                   << " does not contain _id or shard key for pattern "
                                   << _manager->getShardKeyPattern().toString());
        }
    }

    // Target the shard key, query, or replacement doc
    if (!shardKey.isEmpty()) {
        // We can't rely on our query targeting to be exact
        ShardEndpoint* endpoint = NULL;
        Status result =
            targetShardKey(txn, shardKey, (query.objsize() + updateExpr.objsize()), &endpoint);
        endpoints->push_back(endpoint);
        return result;
    } else if (updateType == UpdateType_OpStyle) {
        return targetQuery(query, endpoints);
    } else {
        return targetDoc(updateExpr, endpoints);
    }
}