Skip to content

Commit 84433e7

Browse files
committed
fix(bulk): bulk operations should not throw an error on empty batch
Presently the ordered and unordered bulk operation classes will throw an error when an empty set of operations is provided. This is invalid behavior beause the execute method takes a callback, or returns a promise. The error should be properly pushed back up the chain to the user.
1 parent 295fb3a commit 84433e7

File tree

3 files changed

+103
-42
lines changed

3 files changed

+103
-42
lines changed

lib/bulk/ordered.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -488,30 +488,37 @@ var executeCommands = function(self, callback) {
488488
*/
489489
OrderedBulkOperation.prototype.execute = function(_writeConcern, callback) {
490490
var self = this;
491-
if(this.s.executed) throw new toError("batch cannot be re-executed");
492-
if(typeof _writeConcern == 'function') {
491+
if (this.s.executed) {
492+
var executedError = toError('batch cannot be re-executed');
493+
return (typeof callback === 'function') ?
494+
callback(executedError, null) : this.s.promiseLibrary.reject(executedError);
495+
}
496+
497+
if (typeof _writeConcern === 'function') {
493498
callback = _writeConcern;
494-
} else if(_writeConcern && typeof _writeConcern == 'object') {
499+
} else if (_writeConcern && typeof _writeConcern === 'object') {
495500
this.s.writeConcern = _writeConcern;
496501
}
497502

498503
// If we have current batch
499-
if(this.s.currentBatch) this.s.batches.push(this.s.currentBatch)
504+
if (this.s.currentBatch) this.s.batches.push(this.s.currentBatch)
500505

501506
// If we have no operations in the bulk raise an error
502-
if(this.s.batches.length == 0) {
503-
throw toError("Invalid Operation, No operations in bulk");
507+
if (this.s.batches.length == 0) {
508+
var emptyBatchError = toError('Invalid Operation, no operations specified');
509+
return (typeof callback === 'function') ?
510+
callback(emptyBatchError, null) : this.s.promiseLibrary.reject(emptyBatchError);
504511
}
505512

506513
// Execute using callback
507-
if(typeof callback == 'function') {
508-
return executeCommands(this, callback);
509-
}
514+
if (typeof callback === 'function') {
515+
return executeCommands(this, callback);
516+
}
510517

511518
// Return a Promise
512519
return new this.s.promiseLibrary(function(resolve, reject) {
513520
executeCommands(self, function(err, r) {
514-
if(err) return reject(err);
521+
if (err) return reject(err);
515522
resolve(r);
516523
});
517524
});

lib/bulk/unordered.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -490,30 +490,37 @@ var executeBatches = function(self, callback) {
490490
*/
491491
UnorderedBulkOperation.prototype.execute = function(_writeConcern, callback) {
492492
var self = this;
493-
if(this.s.executed) throw toError("batch cannot be re-executed");
494-
if(typeof _writeConcern == 'function') {
493+
if (this.s.executed) {
494+
var executedError = toError('batch cannot be re-executed');
495+
return (typeof callback === 'function') ?
496+
callback(executedError, null) : this.s.promiseLibrary.reject(executedError);
497+
}
498+
499+
if (typeof _writeConcern === 'function') {
495500
callback = _writeConcern;
496-
} else if(_writeConcern && typeof _writeConcern == 'object') {
501+
} else if (_writeConcern && typeof _writeConcern === 'object') {
497502
this.s.writeConcern = _writeConcern;
498503
}
499504

500505
// If we have current batch
501-
if(this.s.currentInsertBatch) this.s.batches.push(this.s.currentInsertBatch);
502-
if(this.s.currentUpdateBatch) this.s.batches.push(this.s.currentUpdateBatch);
503-
if(this.s.currentRemoveBatch) this.s.batches.push(this.s.currentRemoveBatch);
506+
if (this.s.currentInsertBatch) this.s.batches.push(this.s.currentInsertBatch);
507+
if (this.s.currentUpdateBatch) this.s.batches.push(this.s.currentUpdateBatch);
508+
if (this.s.currentRemoveBatch) this.s.batches.push(this.s.currentRemoveBatch);
504509

505510
// If we have no operations in the bulk raise an error
506-
if(this.s.batches.length == 0) {
507-
throw toError("Invalid Operation, No operations in bulk");
511+
if (this.s.batches.length == 0) {
512+
var emptyBatchError = toError('Invalid Operation, no operations specified');
513+
return (typeof callback === 'function') ?
514+
callback(emptyBatchError, null) : this.s.promiseLibrary.reject(emptyBatchError);
508515
}
509516

510517
// Execute using callback
511-
if(typeof callback == 'function') return executeBatches(this, callback);
518+
if (typeof callback === 'function') return executeBatches(this, callback);
512519

513520
// Return a Promise
514521
return new this.s.promiseLibrary(function(resolve, reject) {
515522
executeBatches(self, function(err, r) {
516-
if(err) return reject(err);
523+
if (err) return reject(err);
517524
resolve(r);
518525
});
519526
});

test/functional/bulk_tests.js

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ exports['Should correctly perform ordered upsert with custom _id'] = {
369369
}
370370
}
371371

372-
exports['Should throw an error when no operations in ordered batch'] = {
372+
exports['Should return an error when no operations in ordered batch'] = {
373373
metadata: { requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } },
374374

375375
// The actual test we wish to run
@@ -378,18 +378,15 @@ exports['Should throw an error when no operations in ordered batch'] = {
378378
db.open(function(err, db) {
379379
// Get the collection
380380
var col = db.collection('batch_write_ordered_ops_8');
381-
var threw = false;
382381

383-
try {
384-
// Initialize the Ordered Batch
385-
col.initializeOrderedBulkOp().execute(function(err, result) {});
386-
} catch(err) {
387-
threw = true;
388-
}
382+
// Initialize the Ordered Batch
383+
col.initializeOrderedBulkOp().execute(function(err, result) {
384+
test.equal(err instanceof Error, true);
385+
test.equal(err.message, 'Invalid Operation, no operations specified');
389386

390-
test.equal(true, threw);
391-
db.close();
392-
test.done();
387+
db.close();
388+
test.done();
389+
});
393390
});
394391
}
395392
}
@@ -874,7 +871,7 @@ exports['Should prohibit batch finds with no selector'] = {
874871
}
875872
}
876873

877-
exports['Should throw an error when no operations in unordered batch'] = {
874+
exports['Should return an error when no operations in unordered batch'] = {
878875
metadata: { requires: { topology: ['single', 'replicaset', 'ssl', 'heap', 'wiredtiger'] } },
879876

880877
// The actual test we wish to run
@@ -883,18 +880,15 @@ exports['Should throw an error when no operations in unordered batch'] = {
883880
db.open(function(err, db) {
884881
// Get the collection
885882
var col = db.collection('batch_write_ordered_ops_8');
886-
var threw = false;
887883

888-
try {
889-
// Initialize the Ordered Batch
890-
col.initializeUnorderedBulkOp().execute(configuration.writeConcernMax(),function(err, result) {});
891-
} catch(err) {
892-
threw = true;
893-
}
884+
// Initialize the Ordered Batch
885+
col.initializeUnorderedBulkOp().execute(configuration.writeConcernMax(),function(err, result) {
886+
test.equal(err instanceof Error, true);
887+
test.equal(err.message, 'Invalid Operation, no operations specified');
894888

895-
test.equal(true, threw);
896-
db.close();
897-
test.done();
889+
db.close();
890+
test.done();
891+
});
898892
});
899893
}
900894
}
@@ -1228,3 +1222,56 @@ exports['Should correctly handle bulk operation split for unordered bulk operati
12281222
});
12291223
}
12301224
}
1225+
1226+
exports['Should return an error instead of throwing when no operations are provided for ordered bulk operation execute'] = {
1227+
metadata: { requires: { mongodb: ">=2.6.0" , topology: 'single', node: ">0.10.0" } },
1228+
test: function(configure, test) {
1229+
var db = configure.newDbInstance({ w: 1 }, { poolSize: 1 });
1230+
1231+
db.open(function(err, db) {
1232+
db.collection('doesnt_matter').insertMany([], function(err, r) {
1233+
test.equal(err instanceof Error, true);
1234+
test.equal(err.message, 'Invalid Operation, no operations specified');
1235+
db.close();
1236+
test.done();
1237+
});
1238+
});
1239+
}
1240+
}
1241+
1242+
exports['Should return an error instead of throwing when no operations are provided for unordered bulk operation execute'] = {
1243+
metadata: { requires: { mongodb: ">=2.6.0" , topology: 'single', node: ">0.10.0" } },
1244+
test: function(configure, test) {
1245+
var db = configure.newDbInstance({ w: 1 }, { poolSize: 1 });
1246+
1247+
db.open(function(err, db) {
1248+
db.collection('doesnt_matter').insertMany([], { ordered: false }, function(err, r) {
1249+
test.equal(err instanceof Error, true);
1250+
test.equal(err.message, 'Invalid Operation, no operations specified');
1251+
db.close();
1252+
test.done();
1253+
});
1254+
});
1255+
}
1256+
}
1257+
1258+
exports['Should return an error instead of throwing when an empty bulk operation is submitted (with promise)'] = {
1259+
metadata: { requires: { promises: true, node: ">0.12.0" } },
1260+
test: function(configure, test) {
1261+
var db = configure.newDbInstance({ w: 1 }, { poolSize: 1 });
1262+
1263+
return db.open()
1264+
.then(function() { return db.collection('doesnt_matter').insertMany([]); })
1265+
.then(function() {
1266+
test.equal(false, true); // this should not happen!
1267+
})
1268+
.catch(function(err) {
1269+
test.equal(err instanceof Error, true);
1270+
test.equal(err.message, 'Invalid Operation, no operations specified');
1271+
})
1272+
.then(function() {
1273+
db.close();
1274+
test.done();
1275+
});
1276+
}
1277+
}

0 commit comments

Comments
 (0)