void DBClientSafeWriter::safeWrite( DBClientBase* conn, const BatchItemRef& itemRef, LastError* error ) { const BatchedCommandRequest* request = itemRef.getRequest(); try { // Default settings for checkShardVersion const bool authoritative = false; const int tryNum = 1; // We need to set our version using setShardVersion, managed by checkShardVersionCB versionManager.checkShardVersionCB( conn, request->getTargetingNS(), authoritative, tryNum ); if ( request->getBatchType() == BatchedCommandRequest::BatchType_Insert ) { conn->insert( request->getNS(), request->getInsertRequest()->getDocumentsAt( itemRef.getItemIndex() ), 0 ); } else if ( request->getBatchType() == BatchedCommandRequest::BatchType_Update ) { const BatchedUpdateDocument* update = request->getUpdateRequest()->getUpdatesAt( itemRef.getItemIndex() ); conn->update( request->getNS(), update->getQuery(), update->getUpdateExpr(), update->getUpsert(), update->getMulti() ); } else { dassert( request->getBatchType() == BatchedCommandRequest::BatchType_Delete ); const BatchedDeleteDocument* deleteDoc = request->getDeleteRequest()->getDeletesAt( itemRef.getItemIndex() ); conn->remove( request->getNS(), deleteDoc->getQuery(), deleteDoc->getLimit() == 1 /*just one*/); } // Default GLE Options const bool fsync = false; const bool j = false; const int w = 1; const int wtimeout = 0; BSONObj result = conn->getLastErrorDetailed( NamespaceString( request->getNS() ).db() .toString(), fsync, j, w, wtimeout ); SafeWriter::fillLastError( result, error ); } catch ( const DBException& ex ) { error->raiseError( ex.getCode(), ex.toString().c_str() ); } }
Status DBClientSafeWriter::safeWrite( DBClientBase* conn, const BatchItemRef& itemRef, const BSONObj& writeConcern, BSONObj* gleResponse ) { const BatchedCommandRequest* request = itemRef.getRequest(); try { // Default settings for checkShardVersion const bool authoritative = false; const int tryNum = 1; // We need to set our version using setShardVersion, managed by checkShardVersionCB versionManager.checkShardVersionCB( conn, request->getTargetingNS(), authoritative, tryNum ); if ( request->getBatchType() == BatchedCommandRequest::BatchType_Insert ) { conn->insert( request->getNS(), request->getInsertRequest()->getDocumentsAt( itemRef.getItemIndex() ), 0 ); } else if ( request->getBatchType() == BatchedCommandRequest::BatchType_Update ) { const BatchedUpdateDocument* update = request->getUpdateRequest()->getUpdatesAt( itemRef.getItemIndex() ); conn->update( request->getNS(), update->getQuery(), update->getUpdateExpr(), update->getUpsert(), update->getMulti() ); } else { dassert( request->getBatchType() == BatchedCommandRequest::BatchType_Delete ); const BatchedDeleteDocument* deleteDoc = request->getDeleteRequest()->getDeletesAt( itemRef.getItemIndex() ); conn->remove( request->getNS(), deleteDoc->getQuery(), deleteDoc->getLimit() == 1 /*just one*/); } const StringData& dbName = NamespaceString( request->getNS() ).db(); BSONObjBuilder gleCmdB; gleCmdB.append( "getLastError", true ); gleCmdB.appendElements( writeConcern ); conn->runCommand( dbName.toString(), gleCmdB.obj(), *gleResponse ); } catch ( const DBException& ex ) { return ex.toStatus(); } return Status::OK(); }
void WriteBatchExecutor::incOpStats( const BatchItemRef& currWrite ) { if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Insert ) { // No-op, for inserts we increment not on the op but once for each write } else if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Update ) { _opCounters->gotUpdate(); } else { dassert( currWrite.getOpType() == BatchedCommandRequest::BatchType_Delete ); _opCounters->gotDelete(); } }
void WriteBatchExecutor::incOpStats( const BatchItemRef& currWrite ) { if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Insert ) { _opCounters->gotInsert(); } else if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Update ) { _opCounters->gotUpdate(); } else { dassert( currWrite.getOpType() == BatchedCommandRequest::BatchType_Delete ); _opCounters->gotDelete(); } }
/** * Perform a remove operation, which might remove multiple documents. Dispatches to remove code * currently to do most of this. * * Might fault or error, otherwise populates the result. */ static void multiRemove( const BatchItemRef& removeItem, WriteOpResult* result ) { const NamespaceString nss( removeItem.getRequest()->getNS() ); DeleteRequest request( nss ); request.setQuery( removeItem.getDelete()->getQuery() ); request.setMulti( removeItem.getDelete()->getLimit() != 1 ); request.setUpdateOpLog(true); request.setGod( false ); DeleteExecutor executor( &request ); Status status = executor.prepare(); if ( !status.isOK() ) { result->error = toWriteError( status ); return; } // NOTE: Deletes will not fault outside the lock once any data has been written PageFaultRetryableSection pFaultSection; /////////////////////////////////////////// Lock::DBWrite writeLock( nss.ns() ); /////////////////////////////////////////// // Check version once we're locked if ( !checkShardVersion( &shardingState, *removeItem.getRequest(), &result->error ) ) { // Version error return; } // Context once we're locked, to set more details in currentOp() // TODO: better constructor? Client::Context writeContext( nss.ns(), storageGlobalParams.dbpath, false /* don't check version */); try { result->stats.n = executor.execute(); } catch ( const PageFaultException& ex ) { // TODO: An actual data structure that's not an exception for this result->fault = new PageFaultException( ex ); } catch ( const DBException& ex ) { status = ex.toStatus(); if (ErrorCodes::isInterruption(status.code())) { throw; } result->error = toWriteError(status); } }
void WriteBatchExecutor::execUpdate( const BatchItemRef& updateItem, BSONObj* upsertedId, WriteErrorDetail** error ) { // Updates currently do a lot of the lock management internally const BatchedCommandRequest& request = *updateItem.getRequest(); const NamespaceString nss( updateItem.getRequest()->getNS() ); // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, updateItem ) ); incOpStats( updateItem ); WriteOpResult result; { /////////////////////////////////////////// Lock::DBWrite writeLock( nss.ns() ); /////////////////////////////////////////// // Check version once we're locked if ( checkShardVersion( &shardingState, request, &result.error ) ) { // Context once we're locked, to set more details in currentOp() // TODO: better constructor? Client::Context writeContext( nss.ns(), storageGlobalParams.dbpath, false /* don't check version */); multiUpdate( updateItem, &result ); incWriteStats( updateItem, result.stats, result.error, currentOp.get() ); if ( !result.stats.upsertedID.isEmpty() ) { *upsertedId = result.stats.upsertedID.getOwned(); } } } // END CURRENT OP finishCurrentOp( _client, currentOp.get(), result.error ); if ( result.error ) { result.error->setIndex( updateItem.getItemIndex() ); *error = result.releaseError(); } }
void WriteBatchExecutor::execRemove( const BatchItemRef& removeItem, WriteErrorDetail** error ) { // Removes are similar to updates, but page faults are handled externally // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, removeItem ) ); incOpStats( removeItem ); WriteOpResult result; // NOTE: Deletes will not fault outside the lock once any data has been written PageFaultRetryableSection pageFaultSection; while ( true ) { try { multiRemove( removeItem, &result ); break; } catch (PageFaultException& pfe) { pfe.touch(); invariant(!result.getError()); continue; } fassertFailed(17429); } // END CURRENT OP incWriteStats( removeItem, result.getStats(), result.getError(), currentOp.get() ); finishCurrentOp( _client, currentOp.get(), result.getError() ); if ( result.getError() ) { result.getError()->setIndex( removeItem.getItemIndex() ); *error = result.releaseError(); } }
/** * Perform a single index insert into a collection. Requires the index descriptor be * preprocessed and the collection already has been created. * * Might fault or error, otherwise populates the result. */ static void singleCreateIndex( const BatchItemRef& insertItem, const BSONObj& normalIndexDesc, Collection* collection, WriteOpResult* result ) { const string& indexNS = insertItem.getRequest()->getNS(); Lock::assertWriteLocked( indexNS ); try { Status status = collection->getIndexCatalog()->createIndex( normalIndexDesc, true ); if ( status.code() == ErrorCodes::IndexAlreadyExists ) { result->stats.n = 0; } else if ( !status.isOK() ) { result->error = toWriteError( status ); } else { logOp( "i", indexNS.c_str(), normalIndexDesc ); result->stats.n = 1; } } catch ( const PageFaultException& ex ) { // TODO: An actual data structure that's not an exception for this result->fault = new PageFaultException( ex ); } catch ( const DBException& ex ) { result->error = toWriteError( ex.toStatus() ); } }
void WriteBatchExecutor::execUpdate( const BatchItemRef& updateItem, BSONObj* upsertedId, WriteErrorDetail** error ) { // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, updateItem ) ); incOpStats( updateItem ); WriteOpResult result; WriteUnitOfWork wunit(_txn->recoveryUnit()); multiUpdate( _txn, updateItem, &result ); wunit.commit(); if ( !result.getStats().upsertedID.isEmpty() ) { *upsertedId = result.getStats().upsertedID; } // END CURRENT OP incWriteStats( updateItem, result.getStats(), result.getError(), currentOp.get() ); finishCurrentOp( _txn, _client, currentOp.get(), result.getError() ); if ( result.getError() ) { result.getError()->setIndex( updateItem.getItemIndex() ); *error = result.releaseError(); } }
/** * Perform a single insert into a collection. Requires the insert be preprocessed and the * collection already has been created. * * Might fault or error, otherwise populates the result. */ static void singleInsert( const BatchItemRef& insertItem, const BSONObj& normalInsert, Collection* collection, WriteOpResult* result ) { const string& insertNS = insertItem.getRequest()->getNS(); Lock::assertWriteLocked( insertNS ); try { // XXX - are we 100% sure that all !OK statuses do not write a document? StatusWith<DiskLoc> status = collection->insertDocument( normalInsert, true ); if ( !status.isOK() ) { result->error = toWriteError( status.getStatus() ); } else { logOp( "i", insertNS.c_str(), normalInsert ); getDur().commitIfNeeded(); result->stats.n = 1; } } catch ( const PageFaultException& ex ) { // TODO: An actual data structure that's not an exception for this result->fault = new PageFaultException( ex ); } catch ( const DBException& ex ) { result->error = toWriteError( ex.toStatus() ); } }
// Does preprocessing of inserts, special casing for indexes // TODO: Simplify this when indexes aren't here anymore static StatusWith<BSONObj> normalizeInsert( const BatchItemRef& insertItem ) { if ( insertItem.getRequest()->isInsertIndexRequest() ) { StatusWith<BSONObj> normalInsert = fixDocumentForInsert( insertItem.getDocument() ); if ( normalInsert.isOK() && insertItem.getDocument()["ns"].type() != String ) { return StatusWith<BSONObj>( ErrorCodes::BadValue, "tried to create an index " "without specifying namespace" ); } else { return normalInsert; } } else { return fixDocumentForInsert( insertItem.getDocument() ); } }
/** * Perform a remove operation, which might remove multiple documents. Dispatches to remove code * currently to do most of this. * * Might fault or error, otherwise populates the result. */ static void multiRemove( OperationContext* txn, const BatchItemRef& removeItem, WriteOpResult* result ) { const NamespaceString nss( removeItem.getRequest()->getNS() ); DeleteRequest request( nss ); request.setQuery( removeItem.getDelete()->getQuery() ); request.setMulti( removeItem.getDelete()->getLimit() != 1 ); request.setUpdateOpLog(true); request.setGod( false ); DeleteExecutor executor( &request ); Status status = executor.prepare(); if ( !status.isOK() ) { result->setError(toWriteError(status)); return; } /////////////////////////////////////////// Lock::DBWrite writeLock(txn->lockState(), nss.ns()); /////////////////////////////////////////// // Check version once we're locked if (!checkShardVersion(txn, &shardingState, *removeItem.getRequest(), result)) { // Version error return; } // Context once we're locked, to set more details in currentOp() // TODO: better constructor? Client::Context writeContext( nss.ns(), storageGlobalParams.dbpath, false /* don't check version */); try { result->getStats().n = executor.execute(txn, writeContext.db()); } catch ( const DBException& ex ) { status = ex.toStatus(); if (ErrorCodes::isInterruption(status.code())) { throw; } result->setError(toWriteError(status)); } }
void WriteBatchExecutor::incWriteStats( const BatchItemRef& currWrite, const WriteOpStats& stats, const WriteErrorDetail* error, CurOp* currentOp ) { if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Insert ) { // We increment batch inserts like individual inserts _opCounters->gotInsert(); _stats->numInserted += stats.n; _le->nObjects = stats.n; currentOp->debug().ninserted += stats.n; } else if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Update ) { if ( stats.upsertedID.isEmpty() ) { _stats->numMatched += stats.n; _stats->numModified += stats.nModified; } else { ++_stats->numUpserted; } if ( !error ) { _le->recordUpdate( stats.upsertedID.isEmpty() && stats.n > 0, stats.n, stats.upsertedID ); } } else { dassert( currWrite.getOpType() == BatchedCommandRequest::BatchType_Delete ); _stats->numDeleted += stats.n; if ( !error ) { _le->recordDelete( stats.n ); } currentOp->debug().ndeleted += stats.n; } // Errors reported in LastError are handled internally in write ops for now // TODO: Move error reporting out of write op internals? }
void WriteBatchExecutor::incWriteStats( const BatchItemRef& currWrite, const WriteOpStats& stats, const WriteErrorDetail* error, CurOp* currentOp ) { if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Insert ) { _stats->numInserted += stats.n; _le->nObjects = stats.n; currentOp->debug().ninserted += stats.n; } else if ( currWrite.getOpType() == BatchedCommandRequest::BatchType_Update ) { if ( stats.upsertedID.isEmpty() ) { _stats->numMatched += stats.n; _stats->numModified += stats.nModified; } else { ++_stats->numUpserted; } if ( !error ) { _le->recordUpdate( stats.upsertedID.isEmpty() && stats.n > 0, stats.n, stats.upsertedID ); } } else { dassert( currWrite.getOpType() == BatchedCommandRequest::BatchType_Delete ); _stats->numDeleted += stats.n; if ( !error ) { _le->recordDelete( stats.n ); } currentOp->debug().ndeleted += stats.n; } if (error && !_le->disabled) { _le->raiseError(error->getErrCode(), error->getErrMessage().c_str()); } }
/** * Perform a remove operation, which might remove multiple documents. Dispatches to remove code * currently to do most of this. * * Might fault or error, otherwise populates the result. */ static void multiRemove( const BatchItemRef& removeItem, WriteOpResult* result ) { Lock::assertWriteLocked( removeItem.getRequest()->getNS() ); try { long long n = deleteObjects( removeItem.getRequest()->getNS(), removeItem.getDelete()->getQuery(), removeItem.getDelete()->getLimit() == 1, // justOne true, // logOp false // god ); result->stats.n = n; } catch ( const PageFaultException& ex ) { // TODO: An actual data structure that's not an exception for this result->fault = new PageFaultException( ex ); } catch ( const DBException& ex ) { result->error = toWriteError( ex.toStatus() ); } }
static void multiUpdate( OperationContext* txn, const BatchItemRef& updateItem, WriteOpResult* result ) { const NamespaceString nsString(updateItem.getRequest()->getNS()); UpdateRequest request(nsString); request.setQuery(updateItem.getUpdate()->getQuery()); request.setUpdates(updateItem.getUpdate()->getUpdateExpr()); request.setMulti(updateItem.getUpdate()->getMulti()); request.setUpsert(updateItem.getUpdate()->getUpsert()); request.setUpdateOpLog(true); UpdateLifecycleImpl updateLifecycle(true, request.getNamespaceString()); request.setLifecycle(&updateLifecycle); UpdateExecutor executor(&request, &txn->getCurOp()->debug()); Status status = executor.prepare(); if (!status.isOK()) { result->setError(toWriteError(status)); return; } /////////////////////////////////////////// Lock::DBWrite writeLock(txn->lockState(), nsString.ns()); /////////////////////////////////////////// if (!checkShardVersion(txn, &shardingState, *updateItem.getRequest(), result)) return; Client::Context ctx( nsString.ns(), storageGlobalParams.dbpath, false /* don't check version */ ); try { UpdateResult res = executor.execute(txn, ctx.db()); const long long numDocsModified = res.numDocsModified; const long long numMatched = res.numMatched; const BSONObj resUpsertedID = res.upserted; // We have an _id from an insert const bool didInsert = !resUpsertedID.isEmpty(); result->getStats().nModified = didInsert ? 0 : numDocsModified; result->getStats().n = didInsert ? 1 : numMatched; result->getStats().upsertedID = resUpsertedID; } catch (const DBException& ex) { status = ex.toStatus(); if (ErrorCodes::isInterruption(status.code())) { throw; } result->setError(toWriteError(status)); } }
/** * Perform an update operation, which might update multiple documents in the lock. Dispatches * to update code currently to do most of this. * * Might error, otherwise populates the result. */ static void multiUpdate( const BatchItemRef& updateItem, WriteOpResult* result ) { Lock::assertWriteLocked( updateItem.getRequest()->getNS() ); BSONObj queryObj = updateItem.getUpdate()->getQuery(); BSONObj updateObj = updateItem.getUpdate()->getUpdateExpr(); bool multi = updateItem.getUpdate()->getMulti(); bool upsert = updateItem.getUpdate()->getUpsert(); bool didInsert = false; long long numMatched = 0; long long numDocsModified = 0; BSONObj resUpsertedID; try { const NamespaceString requestNs( updateItem.getRequest()->getNS() ); UpdateRequest request( requestNs ); request.setQuery( queryObj ); request.setUpdates( updateObj ); request.setUpsert( upsert ); request.setMulti( multi ); request.setUpdateOpLog(); // TODO(greg) We need to send if we are ignoring the shard version below, // but for now yes UpdateLifecycleImpl updateLifecycle( true, requestNs ); request.setLifecycle( &updateLifecycle ); UpdateResult res = update( request, &cc().curop()->debug() ); numDocsModified = res.numDocsModified; numMatched = res.numMatched; resUpsertedID = res.upserted; // We have an _id from an insert didInsert = !resUpsertedID.isEmpty(); result->stats.nModified = didInsert ? 0 : numDocsModified; result->stats.n = didInsert ? 1 : numMatched; result->stats.upsertedID = resUpsertedID; } catch ( const DBException& ex ) { result->error = toWriteError( ex.toStatus() ); } }
void WriteBatchExecutor::execRemove( const BatchItemRef& removeItem, WriteErrorDetail** error ) { // Removes are similar to updates, but page faults are handled externally // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, removeItem ) ); incOpStats( removeItem ); WriteOpResult result; multiRemove( _txn, removeItem, &result ); // END CURRENT OP incWriteStats( removeItem, result.getStats(), result.getError(), currentOp.get() ); finishCurrentOp( _txn, _client, currentOp.get(), result.getError() ); if ( result.getError() ) { result.getError()->setIndex( removeItem.getItemIndex() ); *error = result.releaseError(); } }
void WriteBatchExecutor::execRemove( const BatchItemRef& removeItem, WriteErrorDetail** error ) { // Removes are similar to updates, but page faults are handled externally // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, removeItem ) ); incOpStats( removeItem ); WriteOpResult result; while ( true ) { multiRemove( removeItem, &result ); if ( !result.fault ) { incWriteStats( removeItem, result.stats, result.error, currentOp.get() ); break; } // // Check page fault out of lock // dassert( result.fault ); result.fault->touch(); result.reset(); } // END CURRENT OP finishCurrentOp( _client, currentOp.get(), result.error ); if ( result.error ) { result.error->setIndex( removeItem.getItemIndex() ); *error = result.releaseError(); } }
void WriteBatchExecutor::execUpdate( const BatchItemRef& updateItem, BSONObj* upsertedId, WriteErrorDetail** error ) { // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, updateItem ) ); incOpStats( updateItem ); WriteOpResult result; multiUpdate( updateItem, &result ); incWriteStats( updateItem, result.stats, result.error, currentOp.get() ); if ( !result.stats.upsertedID.isEmpty() ) { *upsertedId = result.stats.upsertedID; } // END CURRENT OP finishCurrentOp( _client, currentOp.get(), result.error ); if ( result.error ) { result.error->setIndex( updateItem.getItemIndex() ); *error = result.releaseError(); } }
bool WriteBatchExecutor::applyWriteItem( const BatchItemRef& itemRef, WriteStats* stats, BSONObj* upsertedID, BatchedErrorDetail* error ) { const BatchedCommandRequest& request = *itemRef.getRequest(); const string& ns = request.getNS(); // Clear operation's LastError before starting. _le->reset( true ); //uint64_t itemTimeMicros = 0; bool opSuccess = true; // Each write operation executes in its own PageFaultRetryableSection. This means that // a single batch can throw multiple PageFaultException's, which is not the case for // other operations. PageFaultRetryableSection s; while ( true ) { try { // Execute the write item as a child operation of the current operation. CurOp childOp( _client, _client->curop() ); HostAndPort remote = _client->hasRemote() ? _client->getRemote() : HostAndPort( "0.0.0.0", 0 ); // TODO Modify CurOp "wrapped" constructor to take an opcode, so calling .reset() // is unneeded childOp.reset( remote, getOpCode( request.getBatchType() ) ); childOp.ensureStarted(); OpDebug& opDebug = childOp.debug(); opDebug.ns = ns; { Lock::DBWrite dbLock( ns ); Client::Context ctx( ns, storageGlobalParams.dbpath, // TODO: better constructor? false /* don't check version here */); opSuccess = doWrite( ns, itemRef, &childOp, stats, upsertedID, error ); } childOp.done(); //itemTimeMicros = childOp.totalTimeMicros(); opDebug.executionTime = childOp.totalTimeMillis(); opDebug.recordStats(); // Log operation if running with at least "-v", or if exceeds slow threshold. if (logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(1)) || opDebug.executionTime > serverGlobalParams.slowMS + childOp.getExpectedLatencyMs()) { MONGO_TLOG(1) << opDebug.report( childOp ) << endl; } // TODO Log operation if logLevel >= 3 and assertion thrown (as assembleResponse() // does). // Save operation to system.profile if shouldDBProfile(). if ( childOp.shouldDBProfile( opDebug.executionTime ) ) { profile( *_client, getOpCode( request.getBatchType() ), childOp ); } break; } catch ( PageFaultException& e ) { e.touch(); } } return opSuccess; }
bool WriteBatchExecutor::doWrite( const string& ns, const BatchItemRef& itemRef, CurOp* currentOp, WriteStats* stats, BSONObj* upsertedID, BatchedErrorDetail* error ) { const BatchedCommandRequest& request = *itemRef.getRequest(); int index = itemRef.getItemIndex(); // // Check our shard version if we need to (must be in the write lock) // if ( shardingState.enabled() && request.isShardVersionSet() && !ChunkVersion::isIgnoredVersion( request.getShardVersion() ) ) { Lock::assertWriteLocked( ns ); CollectionMetadataPtr metadata = shardingState.getCollectionMetadata( ns ); ChunkVersion shardVersion = metadata ? metadata->getShardVersion() : ChunkVersion::UNSHARDED(); if ( !request.getShardVersion() // .isWriteCompatibleWith( shardVersion ) ) { // Write stale error to results error->setErrCode( ErrorCodes::StaleShardVersion ); BSONObjBuilder infoB; shardVersion.addToBSON( infoB, "vWanted" ); error->setErrInfo( infoB.obj() ); string errMsg = mongoutils::str::stream() << "stale shard version detected before write, received " << request.getShardVersion().toString() << " but local version is " << shardVersion.toString(); error->setErrMessage( errMsg ); return false; } } // // Not stale, do the actual write // if ( request.getBatchType() == BatchedCommandRequest::BatchType_Insert ) { // Insert return doInsert( ns, request.getInsertRequest()->getDocumentsAt( index ), currentOp, stats, error ); } else if ( request.getBatchType() == BatchedCommandRequest::BatchType_Update ) { // Update return doUpdate( ns, *request.getUpdateRequest()->getUpdatesAt( index ), currentOp, stats, upsertedID, error ); } else { dassert( request.getBatchType() == BatchedCommandRequest::BatchType_Delete ); // Delete return doDelete( ns, *request.getDeleteRequest()->getDeletesAt( index ), currentOp, stats, error ); } }
void WriteBatchExecutor::execRemove( const BatchItemRef& removeItem, WriteErrorDetail** error ) { // Removes are similar to updates, but page faults are handled externally const BatchedCommandRequest& request = *removeItem.getRequest(); const NamespaceString nss( removeItem.getRequest()->getNS() ); // BEGIN CURRENT OP scoped_ptr<CurOp> currentOp( beginCurrentOp( _client, removeItem ) ); incOpStats( removeItem ); WriteOpResult result; while ( true ) { { // NOTE: Deletes will not fault outside the lock once any data has been written PageFaultRetryableSection pFaultSection; /////////////////////////////////////////// Lock::DBWrite writeLock( nss.ns() ); /////////////////////////////////////////// // Check version once we're locked if ( !checkShardVersion( &shardingState, request, &result.error ) ) { // Version error break; } // Context once we're locked, to set more details in currentOp() // TODO: better constructor? Client::Context writeContext( nss.ns(), storageGlobalParams.dbpath, false /* don't check version */); multiRemove( removeItem, &result ); if ( !result.fault ) { incWriteStats( removeItem, result.stats, result.error, currentOp.get() ); break; } } // // Check page fault out of lock // dassert( result.fault ); result.fault->touch(); result.reset(); } // END CURRENT OP finishCurrentOp( _client, currentOp.get(), result.error ); if ( result.error ) { result.error->setIndex( removeItem.getItemIndex() ); *error = result.releaseError(); } }
bool WriteBatchExecutor::doWrite( const string& ns, const BatchItemRef& itemRef, CurOp* currentOp, WriteStats* stats, BSONObj* upsertedID, BatchedErrorDetail* error ) { const BatchedCommandRequest& request = *itemRef.getRequest(); int index = itemRef.getItemIndex(); // // Check our shard version if we need to (must be in the write lock) // CollectionMetadataPtr metadata; if ( shardingState.enabled() ) { // Index inserts make the namespace nontrivial for versioning string targetingNS = itemRef.getRequest()->getTargetingNS(); Lock::assertWriteLocked( targetingNS ); metadata = shardingState.getCollectionMetadata( targetingNS ); if ( request.isShardVersionSet() && !ChunkVersion::isIgnoredVersion( request.getShardVersion() ) ) { ChunkVersion shardVersion = metadata ? metadata->getShardVersion() : ChunkVersion::UNSHARDED(); if ( !request.getShardVersion() // .isWriteCompatibleWith( shardVersion ) ) { buildStaleError( request.getShardVersion(), shardVersion, error ); return false; } } } // // Not stale, do the actual write // if ( request.getBatchType() == BatchedCommandRequest::BatchType_Insert ) { // Need to check for unique index problems if ( metadata && request.isUniqueIndexRequest() ) { if ( !isUniqueIndexCompatible( metadata->getKeyPattern(), request.getIndexKeyPattern() ) ) { buildUniqueIndexError( metadata->getKeyPattern(), request.getIndexKeyPattern(), error ); return false; } } // Insert return doInsert( ns, request.getInsertRequest()->getDocumentsAt( index ), currentOp, stats, error ); } else if ( request.getBatchType() == BatchedCommandRequest::BatchType_Update ) { // TODO: Pass down immutable shard key fields // Update return doUpdate( ns, *request.getUpdateRequest()->getUpdatesAt( index ), currentOp, stats, upsertedID, error ); } else { dassert( request.getBatchType() == BatchedCommandRequest::BatchType_Delete ); // Delete return doDelete( ns, *request.getDeleteRequest()->getDeletesAt( index ), currentOp, stats, error ); } }
Status Strategy::commandOpWrite(const std::string& dbName, const BSONObj& command, BatchItemRef targetingBatchItem, std::vector<CommandResult>* results) { // Note that this implementation will not handle targeting retries and does not completely // emulate write behavior ChunkManagerTargeter targeter(NamespaceString( targetingBatchItem.getRequest()->getTargetingNS())); Status status = targeter.init(); if (!status.isOK()) return status; OwnedPointerVector<ShardEndpoint> endpointsOwned; vector<ShardEndpoint*>& endpoints = endpointsOwned.mutableVector(); if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Insert) { ShardEndpoint* endpoint; Status status = targeter.targetInsert(targetingBatchItem.getDocument(), &endpoint); if (!status.isOK()) return status; endpoints.push_back(endpoint); } else if (targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Update) { Status status = targeter.targetUpdate(*targetingBatchItem.getUpdate(), &endpoints); if (!status.isOK()) return status; } else { invariant(targetingBatchItem.getOpType() == BatchedCommandRequest::BatchType_Delete); Status status = targeter.targetDelete(*targetingBatchItem.getDelete(), &endpoints); if (!status.isOK()) return status; } DBClientShardResolver resolver; DBClientMultiCommand dispatcher; // Assemble requests for (vector<ShardEndpoint*>::const_iterator it = endpoints.begin(); it != endpoints.end(); ++it) { const ShardEndpoint* endpoint = *it; ConnectionString host; Status status = resolver.chooseWriteHost(endpoint->shardName, &host); if (!status.isOK()) return status; RawBSONSerializable request(command); dispatcher.addCommand(host, dbName, request); } // Errors reported when recv'ing responses dispatcher.sendAll(); Status dispatchStatus = Status::OK(); // Recv responses while (dispatcher.numPending() > 0) { ConnectionString host; RawBSONSerializable response; Status status = dispatcher.recvAny(&host, &response); if (!status.isOK()) { // We always need to recv() all the sent operations dispatchStatus = status; continue; } CommandResult result; result.target = host; result.shardTarget = Shard::make(host.toString()); result.result = response.toBSON(); results->push_back(result); } return dispatchStatus; }