BatchedCommandRequest* msgToBatchUpdate( const Message& updateMsg ) { // Parsing DbMessage throws DbMessage dbMsg( updateMsg ); NamespaceString nss( dbMsg.getns() ); int flags = dbMsg.pullInt(); bool upsert = flags & UpdateOption_Upsert; bool multi = flags & UpdateOption_Multi; const BSONObj query = dbMsg.nextJsObj(); const BSONObj updateExpr = dbMsg.nextJsObj(); // No exceptions from here on BatchedUpdateDocument* updateDoc = new BatchedUpdateDocument; updateDoc->setQuery( query ); updateDoc->setUpdateExpr( updateExpr ); updateDoc->setUpsert( upsert ); updateDoc->setMulti( multi ); BatchedCommandRequest* request = new BatchedCommandRequest( BatchedCommandRequest::BatchType_Update ); request->setNS( nss.ns() ); request->getUpdateRequest()->addToUpdates( updateDoc ); return request; }
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); } }