Skip to content

Commit 9271719

Browse files
committed
feat(model): make bulkWrite results include MongoDB bulk write errors as well as validation errors
Fix Automattic#15265
1 parent 483c43d commit 9271719

File tree

3 files changed

+89
-18
lines changed

3 files changed

+89
-18
lines changed

lib/model.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,11 +3104,9 @@ Model.$__insertMany = function(arr, options, callback) {
31043104
const res = {
31053105
acknowledged: true,
31063106
insertedCount: 0,
3107-
insertedIds: {},
3108-
mongoose: {
3109-
validationErrors: validationErrors
3110-
}
3107+
insertedIds: {}
31113108
};
3109+
decorateBulkWriteResult(res, validationErrors, validationErrors);
31123110
return callback(null, res);
31133111
}
31143112
callback(null, []);
@@ -3161,10 +3159,7 @@ Model.$__insertMany = function(arr, options, callback) {
31613159

31623160
// Decorate with mongoose validation errors in case of unordered,
31633161
// because then still do `insertMany()`
3164-
res.mongoose = {
3165-
validationErrors: validationErrors,
3166-
results: results
3167-
};
3162+
decorateBulkWriteResult(res, validationErrors, results);
31683163
}
31693164
return callback(null, res);
31703165
}
@@ -3198,10 +3193,7 @@ Model.$__insertMany = function(arr, options, callback) {
31983193
if (error.writeErrors != null) {
31993194
for (let i = 0; i < error.writeErrors.length; ++i) {
32003195
const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3201-
error.writeErrors[i] = {
3202-
...error.writeErrors[i],
3203-
index: originalIndex
3204-
};
3196+
error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
32053197
if (!ordered) {
32063198
results[originalIndex] = error.writeErrors[i];
32073199
}
@@ -3245,10 +3237,7 @@ Model.$__insertMany = function(arr, options, callback) {
32453237
});
32463238

32473239
if (rawResult && ordered === false) {
3248-
error.mongoose = {
3249-
validationErrors: validationErrors,
3250-
results: results
3251-
};
3240+
decorateBulkWriteResult(error, validationErrors, results);
32523241
}
32533242

32543243
callback(error, null);
@@ -3486,8 +3475,14 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
34863475
then(res => ([res, null])).
34873476
catch(error => ([null, error]));
34883477

3478+
const writeErrorsByIndex = {};
3479+
if (error?.writeErrors) {
3480+
for (const writeError of error.writeErrors) {
3481+
writeErrorsByIndex[writeError.err.index] = writeError;
3482+
}
3483+
}
34893484
for (let i = 0; i < validOpIndexes.length; ++i) {
3490-
results[validOpIndexes[i]] = null;
3485+
results[validOpIndexes[i]] = writeErrorsByIndex[i] ?? null;
34913486
}
34923487
if (error) {
34933488
if (validationErrors.length > 0) {

test/model.test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
const sinon = require('sinon');
77
const start = require('./common');
88

9+
const CastError = require('../lib/error/cast');
910
const assert = require('assert');
1011
const { once } = require('events');
1112
const random = require('./util').random;
@@ -4707,6 +4708,46 @@ describe('Model', function() {
47074708
assert.equal(err.validationErrors[0].path, 'age');
47084709
assert.equal(err.results[0].path, 'age');
47094710
});
4711+
4712+
it('bulkWrite should return both write errors and validation errors in error.results (gh-15265)', async function() {
4713+
const userSchema = new Schema({ _id: Number, age: { type: Number } });
4714+
const User = db.model('User', userSchema);
4715+
4716+
const createdUser = await User.create({ _id: 1, name: 'Test' });
4717+
4718+
const err = await User.bulkWrite([
4719+
{
4720+
updateOne: {
4721+
filter: { _id: createdUser._id },
4722+
update: { $set: { age: 'NaN' } }
4723+
}
4724+
},
4725+
{
4726+
insertOne: {
4727+
document: { _id: 3, age: 14 }
4728+
}
4729+
},
4730+
{
4731+
insertOne: {
4732+
document: { _id: 1, age: 13 }
4733+
}
4734+
},
4735+
{
4736+
insertOne: {
4737+
document: { _id: 1, age: 14 }
4738+
}
4739+
}
4740+
], { ordered: false, throwOnValidationError: true })
4741+
.then(() => null)
4742+
.catch(err => err);
4743+
4744+
assert.ok(err);
4745+
assert.strictEqual(err.mongoose.results.length, 4);
4746+
assert.ok(err.mongoose.results[0] instanceof CastError);
4747+
assert.strictEqual(err.mongoose.results[1], null);
4748+
assert.equal(err.mongoose.results[2].constructor.name, 'WriteError');
4749+
assert.equal(err.mongoose.results[3].constructor.name, 'WriteError');
4750+
});
47104751
});
47114752

47124753
it('deleteOne with cast error (gh-5323)', async function() {
@@ -7060,6 +7101,41 @@ describe('Model', function() {
70607101
assert.deepStrictEqual(docs.map(doc => doc.age), [12, 12]);
70617102
});
70627103

7104+
it('insertMany should return both write errors and validation errors in error.results (gh-15265)', async function() {
7105+
const userSchema = new Schema({ _id: Number, age: { type: Number } });
7106+
const User = db.model('User', userSchema);
7107+
await User.insertOne({ _id: 1, age: 12 });
7108+
7109+
const err = await User.insertMany([
7110+
{ _id: 1, age: 'NaN' },
7111+
{ _id: 3, age: 14 },
7112+
{ _id: 1, age: 13 },
7113+
{ _id: 1, age: 14 }
7114+
], { ordered: false }).then(() => null).catch(err => err);
7115+
7116+
assert.ok(err);
7117+
assert.strictEqual(err.results.length, 4);
7118+
assert.ok(err.results[0] instanceof ValidationError);
7119+
assert.ok(err.results[1] instanceof User);
7120+
assert.ok(err.results[2].err);
7121+
assert.ok(err.results[3].err);
7122+
});
7123+
7124+
it('insertMany should return both write errors and validation errors in error.results with rawResult (gh-15265)', async function() {
7125+
const userSchema = new Schema({ _id: Number, age: { type: Number } });
7126+
const User = db.model('User', userSchema);
7127+
7128+
const res = await User.insertMany([
7129+
{ _id: 1, age: 'NaN' },
7130+
{ _id: 3, age: 14 }
7131+
], { ordered: false, rawResult: true });
7132+
7133+
assert.ok(res);
7134+
assert.strictEqual(res.mongoose.results.length, 2);
7135+
assert.ok(res.mongoose.results[0] instanceof ValidationError);
7136+
assert.ok(res.mongoose.results[1] instanceof User);
7137+
});
7138+
70637139
it('returns writeResult on success', async() => {
70647140

70657141
const userSchema = new Schema({

types/models.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ declare module 'mongoose' {
308308
bulkWrite<DocContents = TRawDocType>(
309309
writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
310310
options: MongooseBulkWriteOptions & { ordered: false }
311-
): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[], results: Array<Error | null> } }>;
311+
): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[], results: Array<Error | mongodb.WriteError | null> } }>;
312312
bulkWrite<DocContents = TRawDocType>(
313313
writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
314314
options?: MongooseBulkWriteOptions

0 commit comments

Comments
 (0)