summaryrefslogtreecommitdiff
path: root/src/mongo/shell/bulk_api.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/shell/bulk_api.js')
-rw-r--r--src/mongo/shell/bulk_api.js1194
1 files changed, 1194 insertions, 0 deletions
diff --git a/src/mongo/shell/bulk_api.js b/src/mongo/shell/bulk_api.js
new file mode 100644
index 00000000000..6d056064f81
--- /dev/null
+++ b/src/mongo/shell/bulk_api.js
@@ -0,0 +1,1194 @@
+//
+// Scope for the function
+//
+var _bulk_api_module = (function() {
+
+ // Batch types
+ var NONE = 0;
+ var INSERT = 1;
+ var UPDATE = 2;
+ var REMOVE = 3
+
+ // Error codes
+ var UNKNOWN_ERROR = 8;
+ var WRITE_CONCERN_FAILED = 64;
+ var UNKNOWN_REPL_WRITE_CONCERN = 79;
+ var NOT_MASTER = 10107;
+
+ // Constants
+ var IndexCollPattern = new RegExp('system\.indexes$');
+
+ /**
+ * Helper function to define properties
+ */
+ var defineReadOnlyProperty = function(self, name, value) {
+ Object.defineProperty(self, name, {
+ enumerable: true
+ , get: function() {
+ return value;
+ }
+ });
+ }
+
+ /**
+ * Shell representation of WriteConcern, possibly includes:
+ * j: write waits for journal
+ * w: write waits until replicated to number of servers (including primary), or mode (string)
+ * wtimeout: how long to wait for "w" replication
+ * fsync: waits for data flush (either journal, nor database files depending on server conf)
+ *
+ * Accepts { w : x, j : x, wtimeout : x, fsync: x } or w, wtimeout, j
+ */
+ var WriteConcern = function(wValue, wTimeout, jValue) {
+
+ if(!(this instanceof WriteConcern))
+ return new WriteConcern(wValue, wTimeout, jValue);
+
+ var opts = {};
+ if (typeof wValue == 'object') {
+ if (arguments.length == 1)
+ opts = Object.merge(wValue);
+ else
+ throw Error("If the first arg is an Object then no additional args are allowed!")
+ } else {
+ if (typeof wValue != 'undefined')
+ opts.w = wValue;
+ if (typeof wTimeout != 'undefined')
+ opts.wtimeout = wTimeout;
+ if (typeof jValue != 'undefined')
+ opts.j = jValue;
+ }
+
+ // Do basic validation.
+ if (typeof opts.w != 'undefined' && typeof opts.w != 'number' && typeof opts.w != 'string')
+ throw Error("w value must be a number or string but was found to be a " + typeof opts.w)
+ if (typeof opts.w == 'number' && NumberInt( opts.w ).toNumber() < 0)
+ throw Error("Numeric w value must be equal to or larger than 0, not " + opts.w);
+
+ if (typeof opts.wtimeout != 'undefined') {
+ if (typeof opts.wtimeout != 'number')
+ throw Error("wtimeout must be a number, not " + opts.wtimeout);
+ if (NumberInt( opts.wtimeout ).toNumber() < 0)
+ throw Error("wtimeout must be a number greater than 0, not " + opts.wtimeout);
+ }
+
+ if (typeof opts.j != 'undefined' && typeof opts.j != 'boolean')
+ throw Error("j value must be true or false if defined, not " + opts.j);
+
+ this._wc = opts;
+
+ this.toJSON = function() {
+ return Object.merge({}, this._wc);
+ };
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ return tojson(this.toJSON(), indent, nolint);
+ };
+
+ this.toString = function() {
+ return "WriteConcern(" + this.tojson() + ")";
+ };
+
+ this.shellPrint = function() {
+ return this.toString();
+ };
+
+ };
+
+ /**
+ * Wraps the result for write commands and presents a convenient api for accessing
+ * single results & errors (returns the last one if there are multiple).
+ * singleBatchType is passed in on bulk operations consisting of a single batch and
+ * are used to filter the WriteResult to only include relevant result fields.
+ */
+ var WriteResult = function(bulkResult, singleBatchType, writeConcern) {
+
+ if(!(this instanceof WriteResult))
+ return new WriteResult(bulkResult, singleBatchType, writeConcern);
+
+ // Define properties
+ defineReadOnlyProperty(this, "ok", bulkResult.ok);
+ defineReadOnlyProperty(this, "nInserted", bulkResult.nInserted);
+ defineReadOnlyProperty(this, "nUpserted", bulkResult.nUpserted);
+ defineReadOnlyProperty(this, "nMatched", bulkResult.nMatched);
+ defineReadOnlyProperty(this, "nModified", bulkResult.nModified);
+ defineReadOnlyProperty(this, "nRemoved", bulkResult.nRemoved);
+
+ //
+ // Define access methods
+ this.getUpsertedId = function() {
+ if (bulkResult.upserted.length == 0) {
+ return null;
+ }
+
+ return bulkResult.upserted[bulkResult.upserted.length - 1];
+ };
+
+ this.getRawResponse = function() {
+ return bulkResult;
+ };
+
+ this.getWriteError = function() {
+ if (bulkResult.writeErrors.length == 0) {
+ return null;
+ } else {
+ return bulkResult.writeErrors[bulkResult.writeErrors.length - 1];
+ }
+ };
+
+ this.hasWriteError = function() {
+ return this.getWriteError() != null;
+ };
+
+ this.getWriteConcernError = function() {
+ if (bulkResult.writeConcernErrors.length == 0) {
+ return null;
+ } else {
+ return bulkResult.writeConcernErrors[0];
+ }
+ };
+
+ this.hasWriteConcernError = function() {
+ return this.getWriteConcernError() != null;
+ };
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ var result = {}
+
+ if(singleBatchType == INSERT) {
+ result.nInserted = this.nInserted;
+ }
+
+ if(singleBatchType == UPDATE) {
+ result.nMatched = this.nMatched;
+ result.nUpserted = this.nUpserted;
+
+ if(this.nModified != undefined)
+ result.nModified = this.nModified;
+
+ if(Array.isArray(bulkResult.upserted)
+ && bulkResult.upserted.length == 1) {
+ result._id = bulkResult.upserted[0]._id;
+ }
+ }
+
+ if(singleBatchType == REMOVE) {
+ result.nRemoved = bulkResult.nRemoved;
+ }
+
+ if(this.getWriteError() != null) {
+ result.writeError = {};
+ result.writeError.code = this.getWriteError().code;
+ result.writeError.errmsg = this.getWriteError().errmsg;
+ }
+
+ if(this.getWriteConcernError() != null) {
+ result.writeConcernError = this.getWriteConcernError();
+ }
+
+ return tojson(result, indent, nolint);
+ };
+
+ this.toString = function() {
+ // Suppress all output for the write concern w:0, since the client doesn't care.
+ if(writeConcern && writeConcern.w == 0) {
+ return "WriteResult(" + tojson({}) + ")";;
+ }
+ return "WriteResult(" + this.tojson() + ")";
+ };
+
+ this.shellPrint = function() {
+ return this.toString();
+ };
+ };
+
+ /**
+ * Wraps the result for the commands
+ */
+ var BulkWriteResult = function(bulkResult, singleBatchType, writeConcern) {
+
+ if(!(this instanceof BulkWriteResult) && !(this instanceof BulkWriteError))
+ return new BulkWriteResult(bulkResult, singleBatchType, writeConcern);
+
+ // Define properties
+ defineReadOnlyProperty(this, "ok", bulkResult.ok);
+ defineReadOnlyProperty(this, "nInserted", bulkResult.nInserted);
+ defineReadOnlyProperty(this, "nUpserted", bulkResult.nUpserted);
+ defineReadOnlyProperty(this, "nMatched", bulkResult.nMatched);
+ defineReadOnlyProperty(this, "nModified", bulkResult.nModified);
+ defineReadOnlyProperty(this, "nRemoved", bulkResult.nRemoved);
+
+ //
+ // Define access methods
+ this.getUpsertedIds = function() {
+ return bulkResult.upserted;
+ }
+
+ this.getUpsertedIdAt = function(index) {
+ return bulkResult.upserted[index];
+ }
+
+ this.getRawResponse = function() {
+ return bulkResult;
+ }
+
+ this.hasWriteErrors = function() {
+ return bulkResult.writeErrors.length > 0;
+ }
+
+ this.getWriteErrorCount = function() {
+ return bulkResult.writeErrors.length;
+ }
+
+ this.getWriteErrorAt = function(index) {
+ if(index < bulkResult.writeErrors.length) {
+ return bulkResult.writeErrors[index];
+ }
+ return null;
+ }
+
+ //
+ // Get all errors
+ this.getWriteErrors = function() {
+ return bulkResult.writeErrors;
+ }
+
+ this.hasWriteConcernError = function() {
+ return bulkResult.writeConcernErrors.length > 0;
+ }
+
+ this.getWriteConcernError = function() {
+ if(bulkResult.writeConcernErrors.length == 0) {
+ return null;
+ } else if(bulkResult.writeConcernErrors.length == 1) {
+ // Return the error
+ return bulkResult.writeConcernErrors[0];
+ } else {
+
+ // Combine the errors
+ var errmsg = "";
+ for(var i = 0; i < bulkResult.writeConcernErrors.length; i++) {
+ var err = bulkResult.writeConcernErrors[i];
+ errmsg = errmsg + err.errmsg;
+ // TODO: Something better
+ if (i != bulkResult.writeConcernErrors.length - 1) {
+ errmsg = errmsg + " and ";
+ }
+ }
+
+ return new WriteConcernError({ errmsg : errmsg, code : WRITE_CONCERN_FAILED });
+ }
+ }
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ return tojson(bulkResult, indent, nolint);
+ }
+
+ this.toString = function() {
+ // Suppress all output for the write concern w:0, since the client doesn't care.
+ if(writeConcern && writeConcern.w == 0) {
+ return "BulkWriteResult(" + tojson({}) + ")";;
+ }
+ return "BulkWriteResult(" + this.tojson() + ")";
+ }
+
+ this.shellPrint = function() {
+ return this.toString();
+ }
+
+ this.hasErrors = function() {
+ return this.hasWriteErrors() || this.hasWriteConcernError();
+ }
+
+ this.toError = function() {
+ if (this.hasErrors()) {
+
+ // Create a combined error message
+ var message = "";
+ var numWriteErrors = this.getWriteErrorCount();
+ if (numWriteErrors == 1) {
+ message += "write error at item " + this.getWriteErrors()[0].index;
+ }
+ else if (numWriteErrors > 1) {
+ message += numWriteErrors + " write errors";
+ }
+
+ var hasWCError = this.hasWriteConcernError();
+ if (numWriteErrors > 0 && hasWCError) {
+ message += " and ";
+ }
+
+ if (hasWCError) {
+ message += "problem enforcing write concern";
+ }
+ message += " in bulk operation";
+
+ return new BulkWriteError(bulkResult, singleBatchType, writeConcern, message);
+ }
+ else {
+ throw Error("batch was successful, cannot create BulkWriteError");
+ }
+ }
+
+ /**
+ * @return {WriteResult} the simplified results condensed into one.
+ */
+ this.toSingleResult = function() {
+ if(singleBatchType == null) throw Error(
+ "Cannot output single WriteResult from multiple batch result");
+ return new WriteResult(bulkResult, singleBatchType, writeConcern);
+ }
+ };
+
+ /**
+ * Represents a bulk write error, identical to a BulkWriteResult but thrown
+ */
+ var BulkWriteError = function(bulkResult, singleBatchType, writeConcern, message) {
+
+ if(!(this instanceof BulkWriteError))
+ return new BulkWriteError(bulkResult, singleBatchType, writeConcern, message);
+
+ Error.captureStackTrace(this, this.constructor);
+ this.name = 'BulkWriteError';
+ this.message = message || 'unknown bulk write error';
+
+ // Bulk errors are basically bulk results with additional error information
+ BulkWriteResult.apply(this, arguments);
+
+ // Override some particular methods
+ delete this.toError;
+
+ this.toString = function() {
+ return "BulkWriteError(" + this.tojson() + ")";
+ }
+
+ this.toResult = function() {
+ return new BulkWriteResult(bulkResult, singleBatchType, writeConcern);
+ }
+ }
+
+ BulkWriteError.prototype = new Error();
+ BulkWriteError.prototype.constructor = BulkWriteError;
+
+ var getEmptyBulkResult = function() {
+ return {
+ writeErrors: []
+ , writeConcernErrors: []
+ , nInserted: 0
+ , nUpserted: 0
+ , nMatched: 0
+ , nModified: 0
+ , nRemoved: 0
+ , upserted: []
+ };
+ }
+
+ /**
+ * Wraps a command error
+ */
+ var WriteCommandError = function(commandError) {
+
+ if(!(this instanceof WriteCommandError)) return new WriteCommandError(commandError);
+
+ // Define properties
+ defineReadOnlyProperty(this, "code", commandError.code);
+ defineReadOnlyProperty(this, "errmsg", commandError.errmsg);
+
+ Error.captureStackTrace(this, this.constructor);
+ this.name = 'WriteCommandError';
+ this.message = this.errmsg;
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ return tojson(commandError, indent, nolint);
+ }
+
+ this.toString = function() {
+ return "WriteCommandError(" + this.tojson() + ")";
+ }
+
+ this.shellPrint = function() {
+ return this.toString();
+ }
+
+ this.toSingleResult = function() {
+ // This is *only* safe to do with a WriteCommandError from the bulk api when the bulk is
+ // known to be of size == 1
+ var bulkResult = getEmptyBulkResult();
+ bulkResult.writeErrors.push({ code : this.code, index : 0, errmsg : this.errmsg });
+ return new BulkWriteResult(bulkResult, NONE).toSingleResult();
+ }
+ }
+
+ WriteCommandError.prototype = new Error();
+ WriteCommandError.prototype.constructor = WriteCommandError;
+
+ /**
+ * Wraps an error for a single write
+ */
+ var WriteError = function(err) {
+ if(!(this instanceof WriteError)) return new WriteError(err);
+
+ // Define properties
+ defineReadOnlyProperty(this, "code", err.code);
+ defineReadOnlyProperty(this, "index", err.index);
+ defineReadOnlyProperty(this, "errmsg", err.errmsg);
+
+ //
+ // Define access methods
+ this.getOperation = function() {
+ return err.op;
+ }
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ return tojson(err, indent, nolint);
+ }
+
+ this.toString = function() {
+ return "WriteError(" + tojson(err) + ")";
+ }
+
+ this.shellPrint = function() {
+ return this.toString();
+ }
+ }
+
+ /**
+ * Wraps a write concern error
+ */
+ var WriteConcernError = function(err) {
+ if(!(this instanceof WriteConcernError)) return new WriteConcernError(err);
+
+ // Define properties
+ defineReadOnlyProperty(this, "code", err.code);
+ defineReadOnlyProperty(this, "errInfo", err.errInfo);
+ defineReadOnlyProperty(this, "errmsg", err.errmsg);
+
+ /**
+ * @return {string}
+ */
+ this.tojson = function(indent, nolint) {
+ return tojson(err, indent, nolint);
+ }
+
+ this.toString = function() {
+ return "WriteConcernError(" + tojson(err) + ")";
+ }
+
+ this.shellPrint = function() {
+ return this.toString();
+ }
+ }
+
+ /**
+ * Keeps the state of an unordered batch so we can rewrite the results
+ * correctly after command execution
+ */
+ var Batch = function(batchType, originalZeroIndex) {
+ this.originalZeroIndex = originalZeroIndex;
+ this.batchType = batchType;
+ this.operations = [];
+ }
+
+ /**
+ * Wraps a legacy operation so we can correctly rewrite its error
+ */
+ var LegacyOp = function(batchType, operation, index) {
+ this.batchType = batchType;
+ this.index = index;
+ this.operation = operation;
+ }
+
+ /***********************************************************
+ * Wraps the operations done for the batch
+ ***********************************************************/
+ var Bulk = function(collection, ordered) {
+ var self = this;
+ var coll = collection;
+ var executed = false;
+
+ // Set max byte size
+ var maxBatchSizeBytes = 1024 * 1024 * 16;
+ var maxNumberOfDocsInBatch = 1000;
+ var writeConcern = null;
+ var currentOp;
+
+ // Final results
+ var bulkResult = getEmptyBulkResult();
+
+ // Current batch
+ var currentBatch = null;
+ var currentIndex = 0;
+ var currentBatchSize = 0;
+ var currentBatchSizeBytes = 0;
+ var batches = [];
+
+ var defineBatchTypeCounter = function(self, name, type) {
+ Object.defineProperty(self, name, {
+ enumerable: true
+ , get: function() {
+ var counter = 0;
+
+ for(var i = 0; i < batches.length; i++) {
+ if(batches[i].batchType == type) {
+ counter += batches[i].operations.length;
+ }
+ }
+
+ if(currentBatch && currentBatch.batchType == type) {
+ counter += currentBatch.operations.length;
+ }
+
+ return counter;
+ }
+ });
+ }
+
+ defineBatchTypeCounter(this, "nInsertOps", INSERT);
+ defineBatchTypeCounter(this, "nUpdateOps", UPDATE);
+ defineBatchTypeCounter(this, "nRemoveOps", REMOVE);
+
+ // Convert bulk into string
+ this.toString = function() {
+ return this.tojson();
+ }
+
+ this.tojson = function() {
+ return tojson({
+ nInsertOps: this.nInsertOps
+ , nUpdateOps: this.nUpdateOps
+ , nRemoveOps: this.nRemoveOps
+ , nBatches: batches.length + (currentBatch == null ? 0 : 1)
+ })
+ }
+
+ this.getOperations = function() {
+ return batches;
+ }
+
+ var finalizeBatch = function(newDocType) {
+ // Save the batch to the execution stack
+ batches.push(currentBatch);
+
+ // Create a new batch
+ currentBatch = new Batch(newDocType, currentIndex);
+
+ // Reset the current size trackers
+ currentBatchSize = 0;
+ currentBatchSizeBytes = 0;
+ };
+
+ // Add to internal list of documents
+ var addToOperationsList = function(docType, document) {
+
+ if (Array.isArray(document))
+ throw Error("operation passed in cannot be an Array");
+
+ // Get the bsonSize
+ var bsonSize = Object.bsonsize(document);
+
+ // Create a new batch object if we don't have a current one
+ if(currentBatch == null) currentBatch = new Batch(docType, currentIndex);
+
+ // Finalize and create a new batch if this op would take us over the
+ // limits *or* if this op is of a different type
+ if(currentBatchSize + 1 > maxNumberOfDocsInBatch
+ || (currentBatchSize > 0 &&
+ currentBatchSizeBytes + bsonSize >= maxBatchSizeBytes)
+ || currentBatch.batchType != docType) {
+ finalizeBatch(docType);
+ }
+
+ currentBatch.operations.push(document);
+ currentIndex = currentIndex + 1;
+ // Update current batch size
+ currentBatchSize = currentBatchSize + 1;
+ currentBatchSizeBytes = currentBatchSizeBytes + bsonSize;
+ };
+
+ /**
+ * @return {Object} a new document with an _id: ObjectId if _id is not present.
+ * Otherwise, returns the same object passed.
+ */
+ var addIdIfNeeded = function(obj) {
+ if ( typeof( obj._id ) == "undefined" && ! Array.isArray( obj ) ){
+ var tmp = obj; // don't want to modify input
+ obj = {_id: new ObjectId()};
+ for (var key in tmp){
+ obj[key] = tmp[key];
+ }
+ }
+
+ return obj;
+ };
+
+ /**
+ * Add the insert document.
+ *
+ * @param document {Object} the document to insert.
+ */
+ this.insert = function(document) {
+ if (!IndexCollPattern.test(coll.getName())) {
+ collection._validateForStorage(document);
+ }
+
+ return addToOperationsList(INSERT, document);
+ };
+
+ //
+ // Find based operations
+ var findOperations = {
+ update: function(updateDocument) {
+ collection._validateUpdateDoc(updateDocument);
+
+ // Set the top value for the update 0 = multi true, 1 = multi false
+ var upsert = typeof currentOp.upsert == 'boolean' ? currentOp.upsert : false;
+ // Establish the update command
+ var document = {
+ q: currentOp.selector
+ , u: updateDocument
+ , multi: true
+ , upsert: upsert
+ }
+
+ // Clear out current Op
+ currentOp = null;
+ // Add the update document to the list
+ return addToOperationsList(UPDATE, document);
+ },
+
+ updateOne: function(updateDocument) {
+ collection._validateUpdateDoc(updateDocument);
+
+ // Set the top value for the update 0 = multi true, 1 = multi false
+ var upsert = typeof currentOp.upsert == 'boolean' ? currentOp.upsert : false;
+ // Establish the update command
+ var document = {
+ q: currentOp.selector
+ , u: updateDocument
+ , multi: false
+ , upsert: upsert
+ }
+
+ // Clear out current Op
+ currentOp = null;
+ // Add the update document to the list
+ return addToOperationsList(UPDATE, document);
+ },
+
+ replaceOne: function(updateDocument) {
+ findOperations.updateOne(updateDocument);
+ },
+
+ upsert: function() {
+ currentOp.upsert = true;
+ // Return the findOperations
+ return findOperations;
+ },
+
+ removeOne: function() {
+ collection._validateRemoveDoc(currentOp.selector);
+
+ // Establish the removeOne command
+ var document = {
+ q: currentOp.selector
+ , limit: 1
+ }
+
+ // Clear out current Op
+ currentOp = null;
+ // Add the remove document to the list
+ return addToOperationsList(REMOVE, document);
+ },
+
+ remove: function() {
+ collection._validateRemoveDoc(currentOp.selector);
+
+ // Establish the remove command
+ var document = {
+ q: currentOp.selector
+ , limit: 0
+ }
+
+ // Clear out current Op
+ currentOp = null;
+ // Add the remove document to the list
+ return addToOperationsList(REMOVE, document);
+ }
+ }
+
+ //
+ // Start of update and remove operations
+ this.find = function(selector) {
+ if (selector == undefined) throw Error("find() requires query criteria");
+ // Save a current selector
+ currentOp = {
+ selector: selector
+ }
+
+ // Return the find Operations
+ return findOperations;
+ }
+
+ //
+ // Merge write command result into aggregated results object
+ var mergeBatchResults = function(batch, bulkResult, result) {
+
+ // If we have an insert Batch type
+ if(batch.batchType == INSERT) {
+ bulkResult.nInserted = bulkResult.nInserted + result.n;
+ }
+
+ // If we have a remove batch type
+ if(batch.batchType == REMOVE) {
+ bulkResult.nRemoved = bulkResult.nRemoved + result.n;
+ }
+
+ var nUpserted = 0;
+
+ // We have an array of upserted values, we need to rewrite the indexes
+ if(Array.isArray(result.upserted)) {
+
+ nUpserted = result.upserted.length;
+
+ for(var i = 0; i < result.upserted.length; i++) {
+ bulkResult.upserted.push({
+ index: result.upserted[i].index + batch.originalZeroIndex
+ , _id: result.upserted[i]._id
+ });
+ }
+ } else if(result.upserted) {
+
+ nUpserted = 1;
+
+ bulkResult.upserted.push({
+ index: batch.originalZeroIndex
+ , _id: result.upserted
+ });
+ }
+
+ // If we have an update Batch type
+ if(batch.batchType == UPDATE) {
+ bulkResult.nUpserted = bulkResult.nUpserted + nUpserted;
+ bulkResult.nMatched = bulkResult.nMatched + (result.n - nUpserted);
+ if(result.nModified == undefined) {
+ bulkResult.nModified = undefined;
+ } else if(bulkResult.nModified != undefined) {
+ bulkResult.nModified = bulkResult.nModified + result.nModified;
+ }
+ }
+
+ if(Array.isArray(result.writeErrors)) {
+ for(var i = 0; i < result.writeErrors.length; i++) {
+
+ var writeError = {
+ index: batch.originalZeroIndex + result.writeErrors[i].index
+ , code: result.writeErrors[i].code
+ , errmsg: result.writeErrors[i].errmsg
+ , op: batch.operations[result.writeErrors[i].index]
+ };
+
+ bulkResult.writeErrors.push(new WriteError(writeError));
+ }
+ }
+
+ if(result.writeConcernError) {
+ bulkResult.writeConcernErrors.push(new WriteConcernError(result.writeConcernError));
+ }
+ }
+
+ //
+ // Execute the batch
+ var executeBatch = function(batch) {
+ var cmd = null;
+ var result = null;
+
+ // Generate the right update
+ if(batch.batchType == UPDATE) {
+ cmd = { update: coll.getName(), updates: batch.operations, ordered: ordered }
+ } else if(batch.batchType == INSERT) {
+ var transformedInserts = [];
+ batch.operations.forEach(function(insertDoc) {
+ transformedInserts.push(addIdIfNeeded(insertDoc));
+ });
+ batch.operations = transformedInserts;
+
+ cmd = { insert: coll.getName(), documents: batch.operations, ordered: ordered }
+ } else if(batch.batchType == REMOVE) {
+ cmd = { delete: coll.getName(), deletes: batch.operations, ordered: ordered }
+ }
+
+ // If we have a write concern
+ if(writeConcern) {
+ cmd.writeConcern = writeConcern;
+ }
+
+ // Run the command (may throw)
+
+ // Get command collection
+ var cmdColl = collection._db.getCollection('$cmd');
+ // Bypass runCommand to ignore slaveOk and read pref settings
+ result = new DBQuery(collection.getMongo(), collection._db,
+ cmdColl, cmdColl.getFullName(), cmd,
+ {} /* proj */, -1 /* limit */, 0 /* skip */, 0 /* batchSize */,
+ 0 /* flags */).next();
+
+ if(result.ok == 0) {
+ throw new WriteCommandError(result);
+ }
+
+ // Merge the results
+ mergeBatchResults(batch, bulkResult, result);
+ }
+
+ // Execute a single legacy op
+ var executeLegacyOp = function(_legacyOp) {
+ // Handle the different types of operation types
+ if(_legacyOp.batchType == INSERT) {
+ if (Array.isArray(_legacyOp.operation)) {
+ var transformedInserts = [];
+ _legacyOp.operation.forEach(function(insertDoc) {
+ transformedInserts.push(addIdIfNeeded(insertDoc));
+ });
+ _legacyOp.operation = transformedInserts;
+ }
+ else {
+ _legacyOp.operation = addIdIfNeeded(_legacyOp.operation);
+ }
+
+ collection.getMongo().insert(collection.getFullName(),
+ _legacyOp.operation,
+ ordered);
+ } else if(_legacyOp.batchType == UPDATE) {
+ collection.getMongo().update(collection.getFullName(),
+ _legacyOp.operation.q,
+ _legacyOp.operation.u,
+ _legacyOp.operation.upsert,
+ _legacyOp.operation.multi);
+ } else if(_legacyOp.batchType == REMOVE) {
+ var single = Boolean(_legacyOp.operation.limit);
+
+ collection.getMongo().remove(collection.getFullName(),
+ _legacyOp.operation.q,
+ single);
+ }
+ }
+
+ /**
+ * Parses the getLastError response and properly sets the write errors and
+ * write concern errors.
+ * Should kept be up to date with BatchSafeWriter::extractGLEErrors.
+ *
+ * @return {object} an object with the format:
+ *
+ * {
+ * writeError: {object|null} raw write error object without the index.
+ * wcError: {object|null} raw write concern error object.
+ * }
+ */
+ var extractGLEErrors = function(gleResponse) {
+ var isOK = gleResponse.ok? true : false;
+ var err = (gleResponse.err)? gleResponse.err : '';
+ var errMsg = (gleResponse.errmsg)? gleResponse.errmsg : '';
+ var wNote = (gleResponse.wnote)? gleResponse.wnote : '';
+ var jNote = (gleResponse.jnote)? gleResponse.jnote : '';
+ var code = gleResponse.code;
+ var timeout = gleResponse.wtimeout? true : false;
+
+ var extractedErr = { writeError: null, wcError: null, unknownError: null };
+
+ if (err == 'norepl' || err == 'noreplset') {
+ // Know this is legacy gle and the repl not enforced - write concern error in 2.4.
+ var errObj = { code: WRITE_CONCERN_FAILED };
+
+ if (errMsg != '') {
+ errObj.errmsg = errMsg;
+ }
+ else if (wNote != '') {
+ errObj.errmsg = wNote;
+ }
+ else {
+ errObj.errmsg = err;
+ }
+
+ extractedErr.wcError = errObj;
+ }
+ else if (timeout) {
+ // Know there was not write error.
+ var errObj = { code: WRITE_CONCERN_FAILED };
+
+ if (errMsg != '') {
+ errObj.errmsg = errMsg;
+ }
+ else {
+ errObj.errmsg = err;
+ }
+
+ errObj.errInfo = { wtimeout: true };
+ extractedErr.wcError = errObj;
+ }
+ else if (code == 19900 || // No longer primary
+ code == 16805 || // replicatedToNum no longer primary
+ code == 14330 || // gle wmode changed; invalid
+ code == NOT_MASTER ||
+ code == UNKNOWN_REPL_WRITE_CONCERN ||
+ code == WRITE_CONCERN_FAILED) {
+ extractedErr.wcError = {
+ code: code,
+ errmsg: errMsg
+ };
+ }
+ else if (!isOK) {
+ // This is a GLE failure we don't understand
+ extractedErr.unknownError = {
+ code: code
+ , errmsg: errMsg
+ }
+ }
+ else if (err != '') {
+ extractedErr.writeError = {
+ code: (code == 0)? UNKNOWN_ERROR : code,
+ errmsg: err
+ };
+ }
+ else if (jNote != '') {
+ extractedErr.writeError = {
+ code: WRITE_CONCERN_FAILED,
+ errmsg: jNote
+ };
+ }
+
+ // Handling of writeback not needed for mongo shell.
+ return extractedErr;
+ };
+
+ /**
+ * getLastErrorMethod that supports all write concerns
+ */
+ var executeGetLastError = function(db, options) {
+ var cmd = { getlasterror : 1 };
+ cmd = Object.extend(cmd, options);
+ // Execute the getLastErrorCommand
+ return db.runCommand( cmd );
+ };
+
+ // Execute the operations, serially
+ var executeBatchWithLegacyOps = function(batch) {
+
+ var batchResult = {
+ n: 0
+ , writeErrors: []
+ , upserted: []
+ };
+
+ var extractedErr = null;
+
+ var totalToExecute = batch.operations.length;
+ // Run over all the operations
+ for(var i = 0; i < batch.operations.length; i++) {
+
+ if(batchResult.writeErrors.length > 0 && ordered) break;
+
+ var _legacyOp = new LegacyOp(batch.batchType, batch.operations[i], i);
+ executeLegacyOp(_legacyOp);
+
+ var result = executeGetLastError(collection.getDB(), { w: 1 });
+ extractedErr = extractGLEErrors(result);
+
+ if (extractedErr.unknownError) {
+ throw new WriteCommandError({
+ ok : 0.0
+ , code : extractedErr.unknownError.code
+ , errmsg : extractedErr.unknownError.errmsg
+ });
+ }
+
+ if (extractedErr.writeError != null) {
+ // Create the emulated result set
+ var errResult = {
+ index: _legacyOp.index
+ , code: extractedErr.writeError.code
+ , errmsg: extractedErr.writeError.errmsg
+ , op: batch.operations[_legacyOp.index]
+ };
+
+ batchResult.writeErrors.push(errResult);
+ }
+ else if(_legacyOp.batchType == INSERT) {
+ // Inserts don't give us "n" back, so we can only infer
+ batchResult.n = batchResult.n + 1;
+ }
+
+ if(_legacyOp.batchType == UPDATE) {
+
+ // Unfortunately v2.4 GLE does not include the upserted field when
+ // the upserted _id is non-OID type. We can detect this by the
+ // updatedExisting field + an n of 1
+ var upserted = result.upserted !== undefined ||
+ (result.updatedExisting === false && result.n == 1);
+
+ if(upserted) {
+ batchResult.n = batchResult.n + 1;
+
+ // If we don't have an upserted value, see if we can pull it from the update or the
+ // query
+ if (result.upserted === undefined) {
+ result.upserted = _legacyOp.operation.u._id;
+ if (result.upserted === undefined) {
+ result.upserted = _legacyOp.operation.q._id;
+ }
+ }
+
+ batchResult.upserted.push({
+ index: _legacyOp.index
+ , _id: result.upserted
+ });
+ } else if(result.n) {
+ batchResult.n = batchResult.n + result.n;
+ }
+ }
+
+ if(_legacyOp.batchType == REMOVE && result.n) {
+ batchResult.n = batchResult.n + result.n;
+ }
+ }
+
+ var needToEnforceWC = writeConcern != null &&
+ bsonWoCompare(writeConcern, { w: 1 }) != 0 &&
+ bsonWoCompare(writeConcern, { w: 0 }) != 0;
+
+ extractedErr = null;
+ if (needToEnforceWC &&
+ (batchResult.writeErrors.length == 0 ||
+ (!ordered &&
+ // not all errored.
+ batchResult.writeErrors.length < batch.operations.length))) {
+
+ // if last write errored
+ if( batchResult.writeErrors.length > 0 &&
+ batchResult.writeErrors[batchResult.writeErrors.length - 1].index ==
+ (batch.operations.length - 1)) {
+ // Reset previous errors so we can apply the write concern no matter what
+ // as long as it is valid.
+ collection.getDB().runCommand({ resetError: 1 });
+ }
+
+ result = executeGetLastError(collection.getDB(), writeConcern);
+ extractedErr = extractGLEErrors(result);
+
+ if (extractedErr.unknownError) {
+ // Report as a wc failure
+ extractedErr.wcError = extractedErr.unknownError;
+ }
+ }
+
+ if (extractedErr != null && extractedErr.wcError != null) {
+ bulkResult.writeConcernErrors.push(extractedErr.wcError);
+ }
+
+ // Merge the results
+ mergeBatchResults(batch, bulkResult, batchResult);
+ }
+
+ //
+ // Execute the batch
+ this.execute = function(_writeConcern) {
+ if(executed) throw Error("A bulk operation cannot be re-executed");
+
+ // If writeConcern set, use it, else get from collection (which will inherit from db/mongo)
+ writeConcern = _writeConcern ? _writeConcern : coll.getWriteConcern();
+ if (writeConcern instanceof WriteConcern)
+ writeConcern = writeConcern.toJSON();
+
+ // If we have current batch
+ if(currentBatch) batches.push(currentBatch);
+
+ // Total number of batches to execute
+ var totalNumberToExecute = batches.length;
+
+ var useWriteCommands = collection.getMongo().useWriteCommands();
+
+ // Execute all the batches
+ for(var i = 0; i < batches.length; i++) {
+
+ // Execute the batch
+ if(collection.getMongo().hasWriteCommands() &&
+ collection.getMongo().writeMode() == "commands") {
+ executeBatch(batches[i]);
+ } else {
+ executeBatchWithLegacyOps(batches[i]);
+ }
+
+ // If we are ordered and have errors and they are
+ // not all replication errors terminate the operation
+ if(bulkResult.writeErrors.length > 0 && ordered) {
+ // Ordered batches can't enforce full-batch write concern if they fail - they fail-fast
+ bulkResult.writeConcernErrors = [];
+ break;
+ }
+ }
+
+ // Set as executed
+ executed = true;
+
+ // Create final result object
+ typedResult = new BulkWriteResult(bulkResult,
+ batches.length == 1 ? batches[0].batchType : null,
+ writeConcern);
+ // Throw on error
+ if (typedResult.hasErrors()) {
+ throw typedResult.toError();
+ }
+
+ return typedResult;
+ }
+ }
+
+ //
+ // Exports
+ //
+
+ module = {};
+ module.WriteConcern = WriteConcern;
+ module.WriteResult = WriteResult;
+ module.BulkWriteResult = BulkWriteResult;
+ module.BulkWriteError = BulkWriteError;
+ module.WriteCommandError = WriteCommandError;
+ module.initializeUnorderedBulkOp = function() {
+ return new Bulk(this, false);
+ };
+ module.initializeOrderedBulkOp = function() {
+ return new Bulk(this, true);
+ };
+
+ return module;
+
+})();
+
+// Globals
+WriteConcern = _bulk_api_module.WriteConcern;
+WriteResult = _bulk_api_module.WriteResult;
+BulkWriteResult = _bulk_api_module.BulkWriteResult;
+BulkWriteError = _bulk_api_module.BulkWriteError;
+WriteCommandError = _bulk_api_module.WriteCommandError;
+
+/***********************************************************
+ * Adds the initializers of bulk operations to the db collection
+ ***********************************************************/
+DBCollection.prototype.initializeUnorderedBulkOp = _bulk_api_module.initializeUnorderedBulkOp;
+DBCollection.prototype.initializeOrderedBulkOp = _bulk_api_module.initializeOrderedBulkOp;
+