Skip to content

Commit 8632e79

Browse files
committed
feat(model): report validation errors from insertMany() if using ordered: false and rawResult: true
Fix #5337
1 parent 62e2ff6 commit 8632e79

File tree

3 files changed

+56
-8
lines changed

3 files changed

+56
-8
lines changed

lib/model.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,11 @@ var INSERT_MANY_CONVERT_OPTIONS = {
20792079
* because it only sends one operation to the server, rather than one for each
20802080
* document.
20812081
*
2082+
* Mongoose always validates each document **before** sending `insertMany`
2083+
* to MongoDB. So if one document has a validation error, no documents will
2084+
* be saved, unless you set
2085+
* [the `ordered` option to false](https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#error-handling).
2086+
*
20822087
* This function does **not** trigger save middleware.
20832088
*
20842089
* This function triggers the following middleware:
@@ -2091,6 +2096,8 @@ var INSERT_MANY_CONVERT_OPTIONS = {
20912096
*
20922097
* @param {Array|Object|*} doc(s)
20932098
* @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany)
2099+
* @param {Boolean} [options.ordered = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
2100+
* @param {Boolean} [options.rawResult = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `false`, will return the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~insertWriteOpCallback) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`.
20942101
* @param {Function} [callback] callback
20952102
* @return {Promise}
20962103
* @api public
@@ -2105,16 +2112,18 @@ Model.insertMany = function(arr, options, callback) {
21052112
if (callback) {
21062113
callback = this.$wrapCallback(callback);
21072114
}
2108-
var limit = options && options.limit;
2109-
if (typeof limit !== 'number') {
2110-
limit = 1000;
2111-
}
2115+
callback = callback || utils.noop;
2116+
options = options || {};
2117+
var limit = get(options, 'limit', 1000);
2118+
var rawResult = get(options, 'rawResult', false);
2119+
var ordered = get(options, 'ordered', true);
21122120

21132121
if (!Array.isArray(arr)) {
21142122
arr = [arr];
21152123
}
21162124

21172125
var toExecute = [];
2126+
var validationErrors = [];
21182127
arr.forEach(function(doc) {
21192128
toExecute.push(function(callback) {
21202129
doc = new _this(doc);
@@ -2123,7 +2132,8 @@ Model.insertMany = function(arr, options, callback) {
21232132
// Option `ordered` signals that insert should be continued after reaching
21242133
// a failing insert. Therefore we delegate "null", meaning the validation
21252134
// failed. It's up to the next function to filter out all failed models
2126-
if (options != null && typeof options === 'object' && options['ordered'] === false) {
2135+
if (ordered === false) {
2136+
validationErrors.push(error);
21272137
return callback(null, null);
21282138
}
21292139
return callback(error);
@@ -2144,7 +2154,7 @@ Model.insertMany = function(arr, options, callback) {
21442154
});
21452155
// Quickly escape while there aren't any valid docAttributes
21462156
if (docAttributes.length < 1) {
2147-
callback && callback(null, []);
2157+
callback(null, []);
21482158
return;
21492159
}
21502160
var docObjects = docAttributes.map(function(doc) {
@@ -2157,7 +2167,7 @@ Model.insertMany = function(arr, options, callback) {
21572167
return doc.toObject(INSERT_MANY_CONVERT_OPTIONS);
21582168
});
21592169

2160-
_this.collection.insertMany(docObjects, options, function(error) {
2170+
_this.collection.insertMany(docObjects, options, function(error, res) {
21612171
if (error) {
21622172
callback && callback(error);
21632173
return;
@@ -2167,7 +2177,17 @@ Model.insertMany = function(arr, options, callback) {
21672177
docAttributes[i].emit('isNew', false);
21682178
docAttributes[i].constructor.emit('isNew', false);
21692179
}
2170-
callback && callback(null, docAttributes);
2180+
if (rawResult) {
2181+
if (ordered === false) {
2182+
// Decorate with mongoose validation errors in case of unordered,
2183+
// because then still do `insertMany()`
2184+
res.mongoose = {
2185+
validationErrors: validationErrors
2186+
};
2187+
}
2188+
return callback(null, res);
2189+
}
2190+
callback(null, docAttributes);
21712191
});
21722192
});
21732193
};

lib/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,3 +894,9 @@ exports.each = function(arr, fn) {
894894
fn(arr[i]);
895895
}
896896
};
897+
898+
/*!
899+
* ignore
900+
*/
901+
902+
exports.noop = function() {};

test/model.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5398,6 +5398,28 @@ describe('Model', function() {
53985398
});
53995399
});
54005400

5401+
it('insertMany() multi validation error with ordered false (gh-5337)', function(done) {
5402+
var schema = new Schema({
5403+
name: { type: String, required: true }
5404+
});
5405+
var Movie = db.model('gh5337', schema);
5406+
5407+
var arr = [
5408+
{ foo: 'The Phantom Menace' },
5409+
{ name: 'Star Wars' },
5410+
{ name: 'The Empire Strikes Back' },
5411+
{ foobar: 'The Force Awakens' }
5412+
];
5413+
var opts = { ordered: false, rawResult: true };
5414+
Movie.insertMany(arr, opts, function(error, res) {
5415+
assert.ifError(error);
5416+
assert.equal(res.mongoose.validationErrors.length, 2);
5417+
assert.equal(res.mongoose.validationErrors[0].name, 'ValidationError');
5418+
assert.equal(res.mongoose.validationErrors[1].name, 'ValidationError');
5419+
done();
5420+
});
5421+
});
5422+
54015423
it('insertMany() depopulate (gh-4590)', function(done) {
54025424
var personSchema = new Schema({
54035425
name: String

0 commit comments

Comments
 (0)