1 //
  2 // Scope for the function
  3 //
  4 var _bulk_api_module = (function() {
  5 
  6     // Batch types
  7     var NONE = 0;
  8     var INSERT = 1;
  9     var UPDATE = 2;
 10     var REMOVE = 3;
 11 
 12     // Error codes
 13     var UNKNOWN_ERROR = 8;
 14     var WRITE_CONCERN_FAILED = 64;
 15     var UNKNOWN_REPL_WRITE_CONCERN = 79;
 16     var NOT_MASTER = 10107;
 17 
 18     // Constants
 19     var IndexCollPattern = new RegExp('system\.indexes$');
 20 
 21     /**
 22      * Helper function to define properties
 23      */
 24     var defineReadOnlyProperty = function(self, name, value) {
 25         Object.defineProperty(self, name, {
 26             enumerable: true,
 27             get: function() {
 28                 return value;
 29             }
 30         });
 31     };
 32 
 33     /**
 34      * Shell representation of WriteConcern, possibly includes:
 35      *  j: write waits for journal
 36      *  w: write waits until replicated to number of servers (including primary), or mode (string)
 37      *  wtimeout: how long to wait for "w" replication
 38      *  fsync: waits for data flush (either journal, nor database files depending on server conf)
 39      *
 40      * Accepts { w : x, j : x, wtimeout : x, fsync: x } or w, wtimeout, j
 41      */
 42     var WriteConcern = function(wValue, wTimeout, jValue) {
 43 
 44         if (!(this instanceof WriteConcern)) {
 45             var writeConcern = Object.create(WriteConcern.prototype);
 46             WriteConcern.apply(writeConcern, arguments);
 47             return writeConcern;
 48         }
 49 
 50         var opts = {};
 51         if (typeof wValue == 'object') {
 52             if (arguments.length == 1)
 53                 opts = Object.merge(wValue);
 54             else
 55                 throw Error("If the first arg is an Object then no additional args are allowed!");
 56         } else {
 57             if (typeof wValue != 'undefined')
 58                 opts.w = wValue;
 59             if (typeof wTimeout != 'undefined')
 60                 opts.wtimeout = wTimeout;
 61             if (typeof jValue != 'undefined')
 62                 opts.j = jValue;
 63         }
 64 
 65         // Do basic validation.
 66         if (typeof opts.w != 'undefined' && typeof opts.w != 'number' && typeof opts.w != 'string')
 67             throw Error("w value must be a number or string but was found to be a " +
 68                         typeof opts.w);
 69         if (typeof opts.w == 'number' && NumberInt(opts.w).toNumber() < 0)
 70             throw Error("Numeric w value must be equal to or larger than 0, not " + opts.w);
 71 
 72         if (typeof opts.wtimeout != 'undefined') {
 73             if (typeof opts.wtimeout != 'number')
 74                 throw Error("wtimeout must be a number, not " + opts.wtimeout);
 75             if (NumberInt(opts.wtimeout).toNumber() < 0)
 76                 throw Error("wtimeout must be a number greater than or equal to 0, not " +
 77                             opts.wtimeout);
 78         }
 79 
 80         if (typeof opts.j != 'undefined' && typeof opts.j != 'boolean')
 81             throw Error("j value must be true or false if defined, not " + opts.j);
 82 
 83         this._wc = opts;
 84 
 85         this.toJSON = function() {
 86             return Object.merge({}, this._wc);
 87         };
 88 
 89         /**
 90          * @return {string}
 91          */
 92         this.tojson = function(indent, nolint) {
 93             return tojson(this.toJSON(), indent, nolint);
 94         };
 95 
 96         this.toString = function() {
 97             return "WriteConcern(" + this.tojson() + ")";
 98         };
 99 
100         this.shellPrint = function() {
101             return this.toString();
102         };
103 
104     };
105 
106     /**
107      * Wraps the result for write commands and presents a convenient api for accessing
108      * single results & errors (returns the last one if there are multiple).
109      * singleBatchType is passed in on bulk operations consisting of a single batch and
110      * are used to filter the WriteResult to only include relevant result fields.
111      */
112     var WriteResult = function(bulkResult, singleBatchType, writeConcern) {
113 
114         if (!(this instanceof WriteResult))
115             return new WriteResult(bulkResult, singleBatchType, writeConcern);
116 
117         // Define properties
118         defineReadOnlyProperty(this, "ok", bulkResult.ok);
119         defineReadOnlyProperty(this, "nInserted", bulkResult.nInserted);
120         defineReadOnlyProperty(this, "nUpserted", bulkResult.nUpserted);
121         defineReadOnlyProperty(this, "nMatched", bulkResult.nMatched);
122         defineReadOnlyProperty(this, "nModified", bulkResult.nModified);
123         defineReadOnlyProperty(this, "nRemoved", bulkResult.nRemoved);
124 
125         //
126         // Define access methods
127         this.getUpsertedId = function() {
128             if (bulkResult.upserted.length == 0) {
129                 return null;
130             }
131 
132             return bulkResult.upserted[bulkResult.upserted.length - 1];
133         };
134 
135         this.getRawResponse = function() {
136             return bulkResult;
137         };
138 
139         this.getWriteError = function() {
140             if (bulkResult.writeErrors.length == 0) {
141                 return null;
142             } else {
143                 return bulkResult.writeErrors[bulkResult.writeErrors.length - 1];
144             }
145         };
146 
147         this.hasWriteError = function() {
148             return this.getWriteError() != null;
149         };
150 
151         this.getWriteConcernError = function() {
152             if (bulkResult.writeConcernErrors.length == 0) {
153                 return null;
154             } else {
155                 return bulkResult.writeConcernErrors[0];
156             }
157         };
158 
159         this.hasWriteConcernError = function() {
160             return this.getWriteConcernError() != null;
161         };
162 
163         /**
164          * @return {string}
165          */
166         this.tojson = function(indent, nolint) {
167             var result = {};
168 
169             if (singleBatchType == INSERT) {
170                 result.nInserted = this.nInserted;
171             }
172 
173             if (singleBatchType == UPDATE) {
174                 result.nMatched = this.nMatched;
175                 result.nUpserted = this.nUpserted;
176 
177                 if (this.nModified != undefined)
178                     result.nModified = this.nModified;
179 
180                 if (Array.isArray(bulkResult.upserted) && bulkResult.upserted.length == 1) {
181                     result._id = bulkResult.upserted[0]._id;
182                 }
183             }
184 
185             if (singleBatchType == REMOVE) {
186                 result.nRemoved = bulkResult.nRemoved;
187             }
188 
189             if (this.getWriteError() != null) {
190                 result.writeError = {};
191                 result.writeError.code = this.getWriteError().code;
192                 result.writeError.errmsg = this.getWriteError().errmsg;
193             }
194 
195             if (this.getWriteConcernError() != null) {
196                 result.writeConcernError = this.getWriteConcernError();
197             }
198 
199             return tojson(result, indent, nolint);
200         };
201 
202         this.toString = function() {
203             // Suppress all output for the write concern w:0, since the client doesn't care.
204             if (writeConcern && writeConcern.w == 0) {
205                 return "WriteResult(" + tojson({}) + ")";
206             }
207             return "WriteResult(" + this.tojson() + ")";
208         };
209 
210         this.shellPrint = function() {
211             return this.toString();
212         };
213     };
214 
215     /**
216      * Wraps the result for the commands
217      */
218     var BulkWriteResult = function(bulkResult, singleBatchType, writeConcern) {
219 
220         if (!(this instanceof BulkWriteResult) && !(this instanceof BulkWriteError))
221             return new BulkWriteResult(bulkResult, singleBatchType, writeConcern);
222 
223         // Define properties
224         defineReadOnlyProperty(this, "ok", bulkResult.ok);
225         defineReadOnlyProperty(this, "nInserted", bulkResult.nInserted);
226         defineReadOnlyProperty(this, "nUpserted", bulkResult.nUpserted);
227         defineReadOnlyProperty(this, "nMatched", bulkResult.nMatched);
228         defineReadOnlyProperty(this, "nModified", bulkResult.nModified);
229         defineReadOnlyProperty(this, "nRemoved", bulkResult.nRemoved);
230 
231         //
232         // Define access methods
233         this.getUpsertedIds = function() {
234             return bulkResult.upserted;
235         };
236 
237         this.getUpsertedIdAt = function(index) {
238             return bulkResult.upserted[index];
239         };
240 
241         this.getRawResponse = function() {
242             return bulkResult;
243         };
244 
245         this.hasWriteErrors = function() {
246             return bulkResult.writeErrors.length > 0;
247         };
248 
249         this.getWriteErrorCount = function() {
250             return bulkResult.writeErrors.length;
251         };
252 
253         this.getWriteErrorAt = function(index) {
254             if (index < bulkResult.writeErrors.length) {
255                 return bulkResult.writeErrors[index];
256             }
257             return null;
258         };
259 
260         //
261         // Get all errors
262         this.getWriteErrors = function() {
263             return bulkResult.writeErrors;
264         };
265 
266         this.hasWriteConcernError = function() {
267             return bulkResult.writeConcernErrors.length > 0;
268         };
269 
270         this.getWriteConcernError = function() {
271             if (bulkResult.writeConcernErrors.length == 0) {
272                 return null;
273             } else if (bulkResult.writeConcernErrors.length == 1) {
274                 // Return the error
275                 return bulkResult.writeConcernErrors[0];
276             } else {
277                 // Combine the errors
278                 var errmsg = "";
279                 for (var i = 0; i < bulkResult.writeConcernErrors.length; i++) {
280                     var err = bulkResult.writeConcernErrors[i];
281                     errmsg = errmsg + err.errmsg;
282                     // TODO: Something better
283                     if (i != bulkResult.writeConcernErrors.length - 1) {
284                         errmsg = errmsg + " and ";
285                     }
286                 }
287 
288                 return new WriteConcernError({errmsg: errmsg, code: WRITE_CONCERN_FAILED});
289             }
290         };
291 
292         /**
293          * @return {string}
294          */
295         this.tojson = function(indent, nolint) {
296             return tojson(bulkResult, indent, nolint);
297         };
298 
299         this.toString = function() {
300             // Suppress all output for the write concern w:0, since the client doesn't care.
301             if (writeConcern && writeConcern.w == 0) {
302                 return "BulkWriteResult(" + tojson({}) + ")";
303             }
304             return "BulkWriteResult(" + this.tojson() + ")";
305         };
306 
307         this.shellPrint = function() {
308             return this.toString();
309         };
310 
311         this.hasErrors = function() {
312             return this.hasWriteErrors() || this.hasWriteConcernError();
313         };
314 
315         this.toError = function() {
316             if (this.hasErrors()) {
317                 // Create a combined error message
318                 var message = "";
319                 var numWriteErrors = this.getWriteErrorCount();
320                 if (numWriteErrors == 1) {
321                     message += "write error at item " + this.getWriteErrors()[0].index;
322                 } else if (numWriteErrors > 1) {
323                     message += numWriteErrors + " write errors";
324                 }
325 
326                 var hasWCError = this.hasWriteConcernError();
327                 if (numWriteErrors > 0 && hasWCError) {
328                     message += " and ";
329                 }
330 
331                 if (hasWCError) {
332                     message += "problem enforcing write concern";
333                 }
334                 message += " in bulk operation";
335 
336                 return new BulkWriteError(bulkResult, singleBatchType, writeConcern, message);
337             } else {
338                 throw Error("batch was successful, cannot create BulkWriteError");
339             }
340         };
341 
342         /**
343          * @return {WriteResult} the simplified results condensed into one.
344          */
345         this.toSingleResult = function() {
346             if (singleBatchType == null)
347                 throw Error("Cannot output single WriteResult from multiple batch result");
348             return new WriteResult(bulkResult, singleBatchType, writeConcern);
349         };
350     };
351 
352     /**
353      * Represents a bulk write error, identical to a BulkWriteResult but thrown
354      */
355     var BulkWriteError = function(bulkResult, singleBatchType, writeConcern, message) {
356 
357         if (!(this instanceof BulkWriteError))
358             return new BulkWriteError(bulkResult, singleBatchType, writeConcern, message);
359 
360         this.name = 'BulkWriteError';
361         this.message = message || 'unknown bulk write error';
362 
363         // Bulk errors are basically bulk results with additional error information
364         BulkWriteResult.apply(this, arguments);
365 
366         // Override some particular methods
367         delete this.toError;
368 
369         this.toString = function() {
370             return "BulkWriteError(" + this.tojson() + ")";
371         };
372         this.stack = this.toString() + "\n" + (new Error().stack);
373 
374         this.toResult = function() {
375             return new BulkWriteResult(bulkResult, singleBatchType, writeConcern);
376         };
377     };
378 
379     BulkWriteError.prototype = Object.create(Error.prototype);
380     BulkWriteError.prototype.constructor = BulkWriteError;
381 
382     var getEmptyBulkResult = function() {
383         return {
384             writeErrors: [],
385             writeConcernErrors: [],
386             nInserted: 0,
387             nUpserted: 0,
388             nMatched: 0,
389             nModified: 0,
390             nRemoved: 0,
391             upserted: []
392         };
393     };
394 
395     /**
396      * Wraps a command error
397      */
398     var WriteCommandError = function(commandError) {
399 
400         if (!(this instanceof WriteCommandError))
401             return new WriteCommandError(commandError);
402 
403         // Define properties
404         defineReadOnlyProperty(this, "code", commandError.code);
405         defineReadOnlyProperty(this, "errmsg", commandError.errmsg);
406 
407         this.name = 'WriteCommandError';
408         this.message = this.errmsg;
409 
410         /**
411          * @return {string}
412          */
413         this.tojson = function(indent, nolint) {
414             return tojson(commandError, indent, nolint);
415         };
416 
417         this.toString = function() {
418             return "WriteCommandError(" + this.tojson() + ")";
419         };
420         this.stack = this.toString() + "\n" + (new Error().stack);
421 
422         this.shellPrint = function() {
423             return this.toString();
424         };
425 
426         this.toSingleResult = function() {
427             // This is *only* safe to do with a WriteCommandError from the bulk api when the bulk is
428             // known to be of size == 1
429             var bulkResult = getEmptyBulkResult();
430             bulkResult.writeErrors.push({code: this.code, index: 0, errmsg: this.errmsg});
431             return new BulkWriteResult(bulkResult, NONE).toSingleResult();
432         };
433     };
434 
435     WriteCommandError.prototype = Object.create(Error.prototype);
436     WriteCommandError.prototype.constructor = WriteCommandError;
437 
438     /**
439      * Wraps an error for a single write
440      */
441     var WriteError = function(err) {
442         if (!(this instanceof WriteError))
443             return new WriteError(err);
444 
445         this.name = 'WriteError';
446         this.message = err.errmsg || 'unknown write error';
447 
448         // Define properties
449         defineReadOnlyProperty(this, "code", err.code);
450         defineReadOnlyProperty(this, "index", err.index);
451         defineReadOnlyProperty(this, "errmsg", err.errmsg);
452 
453         //
454         // Define access methods
455         this.getOperation = function() {
456             return err.op;
457         };
458 
459         /**
460          * @return {string}
461          */
462         this.tojson = function(indent, nolint) {
463             return tojson(err, indent, nolint);
464         };
465 
466         this.toString = function() {
467             return "WriteError(" + tojson(err) + ")";
468         };
469         this.stack = this.toString() + "\n" + (new Error().stack);
470 
471         this.shellPrint = function() {
472             return this.toString();
473         };
474     };
475 
476     WriteError.prototype = Object.create(Error.prototype);
477     WriteError.prototype.constructor = WriteError;
478 
479     /**
480      * Wraps a write concern error
481      */
482     var WriteConcernError = function(err) {
483         if (!(this instanceof WriteConcernError))
484             return new WriteConcernError(err);
485 
486         this.name = 'WriteConcernError';
487         this.message = err.errmsg || 'unknown write concern error';
488 
489         // Define properties
490         defineReadOnlyProperty(this, "code", err.code);
491         defineReadOnlyProperty(this, "errInfo", err.errInfo);
492         defineReadOnlyProperty(this, "errmsg", err.errmsg);
493 
494         /**
495          * @return {string}
496          */
497         this.tojson = function(indent, nolint) {
498             return tojson(err, indent, nolint);
499         };
500 
501         this.toString = function() {
502             return "WriteConcernError(" + tojson(err) + ")";
503         };
504         this.stack = this.toString() + "\n" + (new Error().stack);
505 
506         this.shellPrint = function() {
507             return this.toString();
508         };
509     };
510 
511     WriteConcernError.prototype = Object.create(Error.prototype);
512     WriteConcernError.prototype.constructor = WriteConcernError;
513 
514     /**
515      * Keeps the state of an unordered batch so we can rewrite the results
516      * correctly after command execution
517      */
518     var Batch = function(batchType, originalZeroIndex) {
519         this.originalZeroIndex = originalZeroIndex;
520         this.batchType = batchType;
521         this.operations = [];
522     };
523 
524     /**
525      * Wraps a legacy operation so we can correctly rewrite its error
526      */
527     var LegacyOp = function(batchType, operation, index) {
528         this.batchType = batchType;
529         this.index = index;
530         this.operation = operation;
531     };
532 
533     /***********************************************************
534      * Wraps the operations done for the batch
535      ***********************************************************/
536     var Bulk = function(collection, ordered) {
537         var self = this;
538         var coll = collection;
539         var executed = false;
540 
541         // Set max byte size
542         var maxBatchSizeBytes = 1024 * 1024 * 16;
543         var maxNumberOfDocsInBatch = 1000;
544         var writeConcern = null;
545         var currentOp;
546 
547         // Final results
548         var bulkResult = getEmptyBulkResult();
549 
550         // Current batch
551         var currentBatch = null;
552         var currentIndex = 0;
553         var currentBatchSize = 0;
554         var currentBatchSizeBytes = 0;
555         var batches = [];
556 
557         var defineBatchTypeCounter = function(self, name, type) {
558             Object.defineProperty(self, name, {
559                 enumerable: true,
560                 get: function() {
561                     var counter = 0;
562 
563                     for (var i = 0; i < batches.length; i++) {
564                         if (batches[i].batchType == type) {
565                             counter += batches[i].operations.length;
566                         }
567                     }
568 
569                     if (currentBatch && currentBatch.batchType == type) {
570                         counter += currentBatch.operations.length;
571                     }
572 
573                     return counter;
574                 }
575             });
576         };
577 
578         defineBatchTypeCounter(this, "nInsertOps", INSERT);
579         defineBatchTypeCounter(this, "nUpdateOps", UPDATE);
580         defineBatchTypeCounter(this, "nRemoveOps", REMOVE);
581 
582         // Convert bulk into string
583         this.toString = function() {
584             return this.tojson();
585         };
586 
587         this.tojson = function() {
588             return tojson({
589                 nInsertOps: this.nInsertOps,
590                 nUpdateOps: this.nUpdateOps,
591                 nRemoveOps: this.nRemoveOps,
592                 nBatches: batches.length + (currentBatch == null ? 0 : 1)
593             });
594         };
595 
596         this.getOperations = function() {
597             return batches;
598         };
599 
600         var finalizeBatch = function(newDocType) {
601             // Save the batch to the execution stack
602             batches.push(currentBatch);
603 
604             // Create a new batch
605             currentBatch = new Batch(newDocType, currentIndex);
606 
607             // Reset the current size trackers
608             currentBatchSize = 0;
609             currentBatchSizeBytes = 0;
610         };
611 
612         // Add to internal list of documents
613         var addToOperationsList = function(docType, document) {
614 
615             if (Array.isArray(document))
616                 throw Error("operation passed in cannot be an Array");
617 
618             // Get the bsonSize
619             var bsonSize = Object.bsonsize(document);
620 
621             // Create a new batch object if we don't have a current one
622             if (currentBatch == null)
623                 currentBatch = new Batch(docType, currentIndex);
624 
625             // Finalize and create a new batch if this op would take us over the
626             // limits *or* if this op is of a different type
627             if (currentBatchSize + 1 > maxNumberOfDocsInBatch ||
628                 (currentBatchSize > 0 && currentBatchSizeBytes + bsonSize >= maxBatchSizeBytes) ||
629                 currentBatch.batchType != docType) {
630                 finalizeBatch(docType);
631             }
632 
633             currentBatch.operations.push(document);
634             currentIndex = currentIndex + 1;
635             // Update current batch size
636             currentBatchSize = currentBatchSize + 1;
637             currentBatchSizeBytes = currentBatchSizeBytes + bsonSize;
638         };
639 
640         /**
641          * @return {Object} a new document with an _id: ObjectId if _id is not present.
642          *     Otherwise, returns the same object passed.
643          */
644         var addIdIfNeeded = function(obj) {
645             if (typeof(obj._id) == "undefined" && !Array.isArray(obj)) {
646                 var tmp = obj;  // don't want to modify input
647                 obj = {_id: new ObjectId()};
648                 for (var key in tmp) {
649                     obj[key] = tmp[key];
650                 }
651             }
652 
653             return obj;
654         };
655 
656         /**
657          * Add the insert document.
658          *
659          * @param document {Object} the document to insert.
660          */
661         this.insert = function(document) {
662             if (!IndexCollPattern.test(coll.getName())) {
663                 collection._validateForStorage(document);
664             }
665 
666             return addToOperationsList(INSERT, document);
667         };
668 
669         //
670         // Find based operations
671         var findOperations = {
672             update: function(updateDocument) {
673                 collection._validateUpdateDoc(updateDocument);
674 
675                 // Set the top value for the update 0 = multi true, 1 = multi false
676                 var upsert = typeof currentOp.upsert == 'boolean' ? currentOp.upsert : false;
677                 // Establish the update command
678                 var document =
679                     {q: currentOp.selector, u: updateDocument, multi: true, upsert: upsert};
680 
681                 // Copy over the collation, if we have one.
682                 if (currentOp.hasOwnProperty('collation')) {
683                     document.collation = currentOp.collation;
684                 }
685 
686                 // Clear out current Op
687                 currentOp = null;
688                 // Add the update document to the list
689                 return addToOperationsList(UPDATE, document);
690             },
691 
692             updateOne: function(updateDocument) {
693                 collection._validateUpdateDoc(updateDocument);
694 
695                 // Set the top value for the update 0 = multi true, 1 = multi false
696                 var upsert = typeof currentOp.upsert == 'boolean' ? currentOp.upsert : false;
697                 // Establish the update command
698                 var document =
699                     {q: currentOp.selector, u: updateDocument, multi: false, upsert: upsert};
700 
701                 // Copy over the collation, if we have one.
702                 if (currentOp.hasOwnProperty('collation')) {
703                     document.collation = currentOp.collation;
704                 }
705 
706                 // Clear out current Op
707                 currentOp = null;
708                 // Add the update document to the list
709                 return addToOperationsList(UPDATE, document);
710             },
711 
712             replaceOne: function(updateDocument) {
713                 findOperations.updateOne(updateDocument);
714             },
715 
716             upsert: function() {
717                 currentOp.upsert = true;
718                 // Return the findOperations
719                 return findOperations;
720             },
721 
722             removeOne: function() {
723                 collection._validateRemoveDoc(currentOp.selector);
724 
725                 // Establish the removeOne command
726                 var document = {q: currentOp.selector, limit: 1};
727 
728                 // Copy over the collation, if we have one.
729                 if (currentOp.hasOwnProperty('collation')) {
730                     document.collation = currentOp.collation;
731                 }
732 
733                 // Clear out current Op
734                 currentOp = null;
735                 // Add the remove document to the list
736                 return addToOperationsList(REMOVE, document);
737             },
738 
739             remove: function() {
740                 collection._validateRemoveDoc(currentOp.selector);
741 
742                 // Establish the remove command
743                 var document = {q: currentOp.selector, limit: 0};
744 
745                 // Copy over the collation, if we have one.
746                 if (currentOp.hasOwnProperty('collation')) {
747                     document.collation = currentOp.collation;
748                 }
749 
750                 // Clear out current Op
751                 currentOp = null;
752                 // Add the remove document to the list
753                 return addToOperationsList(REMOVE, document);
754             },
755 
756             collation: function(collationSpec) {
757                 if (!collection.getMongo().hasWriteCommands()) {
758                     throw new Error(
759                         "cannot use collation if server does not support write commands");
760                 }
761 
762                 if (collection.getMongo().writeMode() !== "commands") {
763                     throw new Error("write mode must be 'commands' in order to use collation, " +
764                                     "but found write mode: " + collection.getMongo().writeMode());
765                 }
766 
767                 currentOp.collation = collationSpec;
768                 return findOperations;
769             },
770         };
771 
772         //
773         // Start of update and remove operations
774         this.find = function(selector) {
775             if (selector == undefined)
776                 throw Error("find() requires query criteria");
777             // Save a current selector
778             currentOp = {selector: selector};
779 
780             // Return the find Operations
781             return findOperations;
782         };
783 
784         //
785         // Merge write command result into aggregated results object
786         var mergeBatchResults = function(batch, bulkResult, result) {
787 
788             // If we have an insert Batch type
789             if (batch.batchType == INSERT) {
790                 bulkResult.nInserted = bulkResult.nInserted + result.n;
791             }
792 
793             // If we have a remove batch type
794             if (batch.batchType == REMOVE) {
795                 bulkResult.nRemoved = bulkResult.nRemoved + result.n;
796             }
797 
798             var nUpserted = 0;
799 
800             // We have an array of upserted values, we need to rewrite the indexes
801             if (Array.isArray(result.upserted)) {
802                 nUpserted = result.upserted.length;
803 
804                 for (var i = 0; i < result.upserted.length; i++) {
805                     bulkResult.upserted.push({
806                         index: result.upserted[i].index + batch.originalZeroIndex,
807                         _id: result.upserted[i]._id
808                     });
809                 }
810             } else if (result.upserted) {
811                 nUpserted = 1;
812 
813                 bulkResult.upserted.push({index: batch.originalZeroIndex, _id: result.upserted});
814             }
815 
816             // If we have an update Batch type
817             if (batch.batchType == UPDATE) {
818                 bulkResult.nUpserted = bulkResult.nUpserted + nUpserted;
819                 bulkResult.nMatched = bulkResult.nMatched + (result.n - nUpserted);
820                 if (result.nModified == undefined) {
821                     bulkResult.nModified = undefined;
822                 } else if (bulkResult.nModified != undefined) {
823                     bulkResult.nModified = bulkResult.nModified + result.nModified;
824                 }
825             }
826 
827             if (Array.isArray(result.writeErrors)) {
828                 for (var i = 0; i < result.writeErrors.length; i++) {
829                     var writeError = {
830                         index: batch.originalZeroIndex + result.writeErrors[i].index,
831                         code: result.writeErrors[i].code,
832                         errmsg: result.writeErrors[i].errmsg,
833                         op: batch.operations[result.writeErrors[i].index]
834                     };
835 
836                     bulkResult.writeErrors.push(new WriteError(writeError));
837                 }
838             }
839 
840             if (result.writeConcernError) {
841                 bulkResult.writeConcernErrors.push(new WriteConcernError(result.writeConcernError));
842             }
843         };
844 
845         //
846         // Constructs the write batch command.
847         var buildBatchCmd = function(batch) {
848             var cmd = null;
849 
850             // Generate the right update
851             if (batch.batchType == UPDATE) {
852                 cmd = {update: coll.getName(), updates: batch.operations, ordered: ordered};
853             } else if (batch.batchType == INSERT) {
854                 var transformedInserts = [];
855                 batch.operations.forEach(function(insertDoc) {
856                     transformedInserts.push(addIdIfNeeded(insertDoc));
857                 });
858                 batch.operations = transformedInserts;
859 
860                 cmd = {insert: coll.getName(), documents: batch.operations, ordered: ordered};
861             } else if (batch.batchType == REMOVE) {
862                 cmd = {delete: coll.getName(), deletes: batch.operations, ordered: ordered};
863             }
864 
865             // If we have a write concern
866             if (writeConcern) {
867                 cmd.writeConcern = writeConcern;
868             }
869 
870             return cmd;
871         };
872 
873         //
874         // Execute the batch
875         var executeBatch = function(batch) {
876             var result = null;
877             var cmd = buildBatchCmd(batch);
878 
879             // Run the command (may throw)
880 
881             // Get command collection
882             var cmdColl = collection._db.getCollection('$cmd');
883             // Bypass runCommand to ignore slaveOk and read pref settings
884             result = new DBQuery(collection.getMongo(),
885                                  collection._db,
886                                  cmdColl,
887                                  cmdColl.getFullName(),
888                                  cmd,
889                                  {} /* proj */,
890                                  -1 /* limit */,
891                                  0 /* skip */,
892                                  0 /* batchSize */,
893                                  0 /* flags */)
894                          .next();
895 
896             if (result.ok == 0) {
897                 throw new WriteCommandError(result);
898             }
899 
900             // Merge the results
901             mergeBatchResults(batch, bulkResult, result);
902         };
903 
904         // Execute a single legacy op
905         var executeLegacyOp = function(_legacyOp) {
906             // Handle the different types of operation types
907             if (_legacyOp.batchType == INSERT) {
908                 if (Array.isArray(_legacyOp.operation)) {
909                     var transformedInserts = [];
910                     _legacyOp.operation.forEach(function(insertDoc) {
911                         transformedInserts.push(addIdIfNeeded(insertDoc));
912                     });
913                     _legacyOp.operation = transformedInserts;
914                 } else {
915                     _legacyOp.operation = addIdIfNeeded(_legacyOp.operation);
916                 }
917 
918                 collection.getMongo().insert(
919                     collection.getFullName(), _legacyOp.operation, ordered);
920             } else if (_legacyOp.batchType == UPDATE) {
921                 collection.getMongo().update(collection.getFullName(),
922                                              _legacyOp.operation.q,
923                                              _legacyOp.operation.u,
924                                              _legacyOp.operation.upsert,
925                                              _legacyOp.operation.multi);
926             } else if (_legacyOp.batchType == REMOVE) {
927                 var single = Boolean(_legacyOp.operation.limit);
928 
929                 collection.getMongo().remove(
930                     collection.getFullName(), _legacyOp.operation.q, single);
931             }
932         };
933 
934         /**
935          * Parses the getLastError response and properly sets the write errors and
936          * write concern errors.
937          * Should kept be up to date with BatchSafeWriter::extractGLEErrors.
938          *
939          * @return {object} an object with the format:
940          *
941          * {
942          *   writeError: {object|null} raw write error object without the index.
943          *   wcError: {object|null} raw write concern error object.
944          * }
945          */
946         var extractGLEErrors = function(gleResponse) {
947             var isOK = gleResponse.ok ? true : false;
948             var err = (gleResponse.err) ? gleResponse.err : '';
949             var errMsg = (gleResponse.errmsg) ? gleResponse.errmsg : '';
950             var wNote = (gleResponse.wnote) ? gleResponse.wnote : '';
951             var jNote = (gleResponse.jnote) ? gleResponse.jnote : '';
952             var code = gleResponse.code;
953             var timeout = gleResponse.wtimeout ? true : false;
954 
955             var extractedErr = {writeError: null, wcError: null, unknownError: null};
956 
957             if (err == 'norepl' || err == 'noreplset') {
958                 // Know this is legacy gle and the repl not enforced - write concern error in 2.4.
959                 var errObj = {code: WRITE_CONCERN_FAILED};
960 
961                 if (errMsg != '') {
962                     errObj.errmsg = errMsg;
963                 } else if (wNote != '') {
964                     errObj.errmsg = wNote;
965                 } else {
966                     errObj.errmsg = err;
967                 }
968 
969                 extractedErr.wcError = errObj;
970             } else if (timeout) {
971                 // Know there was not write error.
972                 var errObj = {code: WRITE_CONCERN_FAILED};
973 
974                 if (errMsg != '') {
975                     errObj.errmsg = errMsg;
976                 } else {
977                     errObj.errmsg = err;
978                 }
979 
980                 errObj.errInfo = {wtimeout: true};
981                 extractedErr.wcError = errObj;
982             } else if (code == 19900 ||  // No longer primary
983                        code == 16805 ||  // replicatedToNum no longer primary
984                        code == 14330 ||  // gle wmode changed; invalid
985                        code == NOT_MASTER ||
986                        code == UNKNOWN_REPL_WRITE_CONCERN || code == WRITE_CONCERN_FAILED) {
987                 extractedErr.wcError = {code: code, errmsg: errMsg};
988             } else if (!isOK) {
989                 // This is a GLE failure we don't understand
990                 extractedErr.unknownError = {code: code, errmsg: errMsg};
991             } else if (err != '') {
992                 extractedErr.writeError = {code: (code == 0) ? UNKNOWN_ERROR : code, errmsg: err};
993             } else if (jNote != '') {
994                 extractedErr.writeError = {code: WRITE_CONCERN_FAILED, errmsg: jNote};
995             }
996 
997             // Handling of writeback not needed for mongo shell.
998             return extractedErr;
999         };
1000 
1001         /**
1002          * getLastErrorMethod that supports all write concerns
1003          */
1004         var executeGetLastError = function(db, options) {
1005             var cmd = {getlasterror: 1};
1006             cmd = Object.extend(cmd, options);
1007             // Execute the getLastErrorCommand
1008             return db.runCommand(cmd);
1009         };
1010 
1011         // Execute the operations, serially
1012         var executeBatchWithLegacyOps = function(batch) {
1013 
1014             var batchResult = {n: 0, writeErrors: [], upserted: []};
1015 
1016             var extractedErr = null;
1017 
1018             var totalToExecute = batch.operations.length;
1019             // Run over all the operations
1020             for (var i = 0; i < batch.operations.length; i++) {
1021                 if (batchResult.writeErrors.length > 0 && ordered)
1022                     break;
1023 
1024                 var _legacyOp = new LegacyOp(batch.batchType, batch.operations[i], i);
1025                 executeLegacyOp(_legacyOp);
1026 
1027                 var result = executeGetLastError(collection.getDB(), {w: 1});
1028                 extractedErr = extractGLEErrors(result);
1029 
1030                 if (extractedErr.unknownError) {
1031                     throw new WriteCommandError({
1032                         ok: 0.0,
1033                         code: extractedErr.unknownError.code,
1034                         errmsg: extractedErr.unknownError.errmsg
1035                     });
1036                 }
1037 
1038                 if (extractedErr.writeError != null) {
1039                     // Create the emulated result set
1040                     var errResult = {
1041                         index: _legacyOp.index,
1042                         code: extractedErr.writeError.code,
1043                         errmsg: extractedErr.writeError.errmsg,
1044                         op: batch.operations[_legacyOp.index]
1045                     };
1046 
1047                     batchResult.writeErrors.push(errResult);
1048                 } else if (_legacyOp.batchType == INSERT) {
1049                     // Inserts don't give us "n" back, so we can only infer
1050                     batchResult.n = batchResult.n + 1;
1051                 }
1052 
1053                 if (_legacyOp.batchType == UPDATE) {
1054                     // Unfortunately v2.4 GLE does not include the upserted field when
1055                     // the upserted _id is non-OID type.  We can detect this by the
1056                     // updatedExisting field + an n of 1
1057                     var upserted = result.upserted !== undefined ||
1058                         (result.updatedExisting === false && result.n == 1);
1059 
1060                     if (upserted) {
1061                         batchResult.n = batchResult.n + 1;
1062 
1063                         // If we don't have an upserted value, see if we can pull it from the update
1064                         // or the
1065                         // query
1066                         if (result.upserted === undefined) {
1067                             result.upserted = _legacyOp.operation.u._id;
1068                             if (result.upserted === undefined) {
1069                                 result.upserted = _legacyOp.operation.q._id;
1070                             }
1071                         }
1072 
1073                         batchResult.upserted.push({index: _legacyOp.index, _id: result.upserted});
1074                     } else if (result.n) {
1075                         batchResult.n = batchResult.n + result.n;
1076                     }
1077                 }
1078 
1079                 if (_legacyOp.batchType == REMOVE && result.n) {
1080                     batchResult.n = batchResult.n + result.n;
1081                 }
1082             }
1083 
1084             var needToEnforceWC = writeConcern != null &&
1085                 bsonWoCompare(writeConcern, {w: 1}) != 0 &&
1086                 bsonWoCompare(writeConcern, {w: 0}) != 0;
1087 
1088             extractedErr = null;
1089             if (needToEnforceWC && (batchResult.writeErrors.length == 0 ||
1090                                     (!ordered &&
1091                                      // not all errored.
1092                                      batchResult.writeErrors.length < batch.operations.length))) {
1093                 // if last write errored
1094                 if (batchResult.writeErrors.length > 0 &&
1095                     batchResult.writeErrors[batchResult.writeErrors.length - 1].index ==
1096                         (batch.operations.length - 1)) {
1097                     // Reset previous errors so we can apply the write concern no matter what
1098                     // as long as it is valid.
1099                     collection.getDB().runCommand({resetError: 1});
1100                 }
1101 
1102                 result = executeGetLastError(collection.getDB(), writeConcern);
1103                 extractedErr = extractGLEErrors(result);
1104 
1105                 if (extractedErr.unknownError) {
1106                     // Report as a wc failure
1107                     extractedErr.wcError = extractedErr.unknownError;
1108                 }
1109             }
1110 
1111             if (extractedErr != null && extractedErr.wcError != null) {
1112                 bulkResult.writeConcernErrors.push(extractedErr.wcError);
1113             }
1114 
1115             // Merge the results
1116             mergeBatchResults(batch, bulkResult, batchResult);
1117         };
1118 
1119         //
1120         // Execute the batch
1121         this.execute = function(_writeConcern) {
1122             if (executed)
1123                 throw Error("A bulk operation cannot be re-executed");
1124 
1125             // If writeConcern set, use it, else get from collection (which will inherit from
1126             // db/mongo)
1127             writeConcern = _writeConcern ? _writeConcern : coll.getWriteConcern();
1128             if (writeConcern instanceof WriteConcern)
1129                 writeConcern = writeConcern.toJSON();
1130 
1131             // If we have current batch
1132             if (currentBatch)
1133                 batches.push(currentBatch);
1134 
1135             // Total number of batches to execute
1136             var totalNumberToExecute = batches.length;
1137 
1138             var useWriteCommands = collection.getMongo().useWriteCommands();
1139 
1140             // Execute all the batches
1141             for (var i = 0; i < batches.length; i++) {
1142                 // Execute the batch
1143                 if (collection.getMongo().hasWriteCommands() &&
1144                     collection.getMongo().writeMode() == "commands") {
1145                     executeBatch(batches[i]);
1146                 } else {
1147                     executeBatchWithLegacyOps(batches[i]);
1148                 }
1149 
1150                 // If we are ordered and have errors and they are
1151                 // not all replication errors terminate the operation
1152                 if (bulkResult.writeErrors.length > 0 && ordered) {
1153                     // Ordered batches can't enforce full-batch write concern if they fail - they
1154                     // fail-fast
1155                     bulkResult.writeConcernErrors = [];
1156                     break;
1157                 }
1158             }
1159 
1160             // Set as executed
1161             executed = true;
1162 
1163             // Create final result object
1164             typedResult = new BulkWriteResult(
1165                 bulkResult, batches.length == 1 ? batches[0].batchType : null, writeConcern);
1166             // Throw on error
1167             if (typedResult.hasErrors()) {
1168                 throw typedResult.toError();
1169             }
1170 
1171             return typedResult;
1172         };
1173 
1174         // Generate an explain command for the bulk operation. Currently we only support single
1175         // batches
1176         // of size 1, which must be either delete or update.
1177         this.convertToExplainCmd = function(verbosity) {
1178             // If we have current batch
1179             if (currentBatch) {
1180                 batches.push(currentBatch);
1181             }
1182 
1183             // We can only explain singleton batches.
1184             if (batches.length !== 1) {
1185                 throw Error("Explained bulk operations must consist of exactly 1 batch");
1186             }
1187 
1188             var explainBatch = batches[0];
1189             var writeCmd = buildBatchCmd(explainBatch);
1190             return {"explain": writeCmd, "verbosity": verbosity};
1191         };
1192     };
1193 
1194     //
1195     // Exports
1196     //
1197 
1198     module = {};
1199     module.WriteConcern = WriteConcern;
1200     module.WriteResult = WriteResult;
1201     module.BulkWriteResult = BulkWriteResult;
1202     module.BulkWriteError = BulkWriteError;
1203     module.WriteCommandError = WriteCommandError;
1204     module.initializeUnorderedBulkOp = function() {
1205         return new Bulk(this, false);
1206     };
1207     module.initializeOrderedBulkOp = function() {
1208         return new Bulk(this, true);
1209     };
1210 
1211     return module;
1212 
1213 })();
1214 
1215 // Globals
1216 WriteConcern = _bulk_api_module.WriteConcern;
1217 WriteResult = _bulk_api_module.WriteResult;
1218 BulkWriteResult = _bulk_api_module.BulkWriteResult;
1219 BulkWriteError = _bulk_api_module.BulkWriteError;
1220 WriteCommandError = _bulk_api_module.WriteCommandError;
1221 
1222 /***********************************************************
1223  * Adds the initializers of bulk operations to the db collection
1224  ***********************************************************/
1225 DBCollection.prototype.initializeUnorderedBulkOp = _bulk_api_module.initializeUnorderedBulkOp;
1226 DBCollection.prototype.initializeOrderedBulkOp = _bulk_api_module.initializeOrderedBulkOp;
1227