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); } }