diff --git a/lib/model.js b/lib/model.js index f1808a42074..ede856bb1a8 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1976,6 +1976,12 @@ Model.insertMany = function(arr, options, callback) { doc = new _this(doc); doc.validate({ __noPromise: true }, function(error) { if (error) { + // Option `ordered` signals that insert should be continued after reaching + // a failing insert. Therefore we delegate "null", meaning the validation + // failed. It's up to the next function to filter out all failed models + if (options != null && typeof options === 'object' && options['ordered'] === false) { + return callback(null, null); + } return callback(error); } callback(null, doc); @@ -1988,7 +1994,16 @@ Model.insertMany = function(arr, options, callback) { callback && callback(error); return; } - var docObjects = docs.map(function(doc) { + // We filter all failed pre-validations by removing nulls + var docAttributes = docs.filter(function(doc) { + return doc != null; + }); + // Quickly escape while there aren't any valid docAttributes + if (docAttributes.length < 1) { + callback && callback(null, []); + return; + } + var docObjects = docAttributes.map(function(doc) { if (doc.schema.options.versionKey) { doc[doc.schema.options.versionKey] = 0; } @@ -2002,12 +2017,12 @@ Model.insertMany = function(arr, options, callback) { callback && callback(error); return; } - for (var i = 0; i < docs.length; ++i) { - docs[i].isNew = false; - docs[i].emit('isNew', false); - docs[i].constructor.emit('isNew', false); + for (var i = 0; i < docAttributes.length; ++i) { + docAttributes[i].isNew = false; + docAttributes[i].emit('isNew', false); + docAttributes[i].constructor.emit('isNew', false); } - callback && callback(null, docs); + callback && callback(null, docAttributes); }); }); }; diff --git a/test/model.test.js b/test/model.test.js index 2193a9f792d..872a1865378 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -5222,7 +5222,7 @@ describe('Model', function() { }); }); - it('insertMany() ordered option (gh-3893)', function(done) { + it('insertMany() ordered option for constraint errors (gh-3893)', function(done) { start.mongodVersion(function(err, version) { if (err) { done(err); @@ -5264,6 +5264,80 @@ describe('Model', function() { } }); + it('insertMany() ordered option for validation errors (gh-5068)', function(done) { + start.mongodVersion(function(err, version) { + if (err) { + done(err); + return; + } + var mongo34 = version[0] > 3 || (version[0] === 3 && version[1] >= 4); + if (!mongo34) { + done(); + return; + } + + test(); + }); + + function test() { + var schema = new Schema({ + name: { type: String, required: true } + }); + var Movie = db.model('gh5068', schema); + + var arr = [ + { name: 'Star Wars' }, + { foo: 'Star Wars' }, + { name: 'The Empire Strikes Back' } + ]; + Movie.insertMany(arr, { ordered: false }, function(error) { + assert.ifError(error); + Movie.find({}).sort({ name: 1 }).exec(function(error, docs) { + assert.ifError(error); + assert.equal(docs.length, 2); + assert.equal(docs[0].name, 'Star Wars'); + assert.equal(docs[1].name, 'The Empire Strikes Back'); + done(); + }); + }); + } + }); + + it('insertMany() ordered option for single validation error', function(done) { + start.mongodVersion(function(err, version) { + if (err) { + done(err); + return; + } + var mongo34 = version[0] > 3 || (version[0] === 3 && version[1] >= 4); + if (!mongo34) { + done(); + return; + } + + test(); + }); + + function test() { + var schema = new Schema({ + name: { type: String, required: true } + }); + var Movie = db.model('gh5068-2', schema); + + var arr = [ + { foo: 'Star Wars' }, + { foo: 'The Fast and the Furious' } + ]; + Movie.insertMany(arr, { ordered: false }, function(error) { + assert.ifError(error); + Movie.find({}).sort({ name: 1 }).exec(function(error, docs) { + assert.equal(docs.length, 0); + done(); + }); + }); + } + }); + it('insertMany() hooks (gh-3846)', function(done) { var schema = new Schema({ name: String