Skip to content

Commit 45bb194

Browse files
authored
Merge pull request #14884 from Automattic/vkarpov15/gh-14763-2
fix(model): throw MongooseBulkSaveIncompleteError if bulkSave() didn't completely succeed
2 parents d28e5a0 + 5716b04 commit 45bb194

File tree

3 files changed

+77
-7
lines changed

3 files changed

+77
-7
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*!
2+
* Module dependencies.
3+
*/
4+
5+
'use strict';
6+
7+
const MongooseError = require('./mongooseError');
8+
9+
10+
/**
11+
* If the underwriting `bulkWrite()` for `bulkSave()` succeeded, but wasn't able to update or
12+
* insert all documents, we throw this error.
13+
*
14+
* @api private
15+
*/
16+
17+
class MongooseBulkSaveIncompleteError extends MongooseError {
18+
constructor(modelName, documents, bulkWriteResult) {
19+
const matchedCount = bulkWriteResult?.matchedCount ?? 0;
20+
const insertedCount = bulkWriteResult?.insertedCount ?? 0;
21+
let preview = documents.map(doc => doc._id).join(', ');
22+
if (preview.length > 100) {
23+
preview = preview.slice(0, 100) + '...';
24+
}
25+
26+
const numDocumentsNotUpdated = documents.length - matchedCount - insertedCount;
27+
super(`${modelName}.bulkSave() was not able to update ${numDocumentsNotUpdated} of the given documents due to incorrect version or optimistic concurrency, document ids: ${preview}`);
28+
29+
this.modelName = modelName;
30+
this.documents = documents;
31+
this.bulkWriteResult = bulkWriteResult;
32+
this.numDocumentsNotUpdated = numDocumentsNotUpdated;
33+
}
34+
}
35+
36+
Object.defineProperty(MongooseBulkSaveIncompleteError.prototype, 'name', {
37+
value: 'MongooseBulkSaveIncompleteError'
38+
});
39+
40+
/*!
41+
* exports
42+
*/
43+
44+
module.exports = MongooseBulkSaveIncompleteError;

lib/model.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const STATES = require('./connectionState');
6464
const util = require('util');
6565
const utils = require('./utils');
6666
const minimize = require('./helpers/minimize');
67+
const MongooseBulkSaveIncompleteError = require('./error/bulkSaveIncompleteError');
6768

6869
const modelCollectionSymbol = Symbol('mongoose#Model#collection');
6970
const modelDbSymbol = Symbol('mongoose#Model#db');
@@ -3418,11 +3419,10 @@ Model.bulkSave = async function bulkSave(documents, options) {
34183419

34193420
const matchedCount = bulkWriteResult?.matchedCount ?? 0;
34203421
const insertedCount = bulkWriteResult?.insertedCount ?? 0;
3421-
if (writeOperations.length > 0 && matchedCount + insertedCount === 0 && !bulkWriteError) {
3422-
throw new DocumentNotFoundError(
3423-
writeOperations.filter(op => op.updateOne).map(op => op.updateOne.filter),
3422+
if (writeOperations.length > 0 && matchedCount + insertedCount < writeOperations.length && !bulkWriteError) {
3423+
throw new MongooseBulkSaveIncompleteError(
34243424
this.modelName,
3425-
writeOperations.length,
3425+
documents,
34263426
bulkWriteResult
34273427
);
34283428
}

test/model.test.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6994,9 +6994,35 @@ describe('Model', function() {
69946994

69956995
foo.bar = 2;
69966996
const err = await TestModel.bulkSave([foo]).then(() => null, err => err);
6997-
assert.equal(err.name, 'DocumentNotFoundError');
6998-
assert.equal(err.numAffected, 1);
6999-
assert.ok(Array.isArray(err.filter));
6997+
assert.equal(err.name, 'MongooseBulkSaveIncompleteError');
6998+
assert.equal(err.numDocumentsNotUpdated, 1);
6999+
});
7000+
it('should error if not all documents were inserted or updated (gh-14763)', async function() {
7001+
const fooSchema = new mongoose.Schema({
7002+
bar: { type: Number }
7003+
}, { optimisticConcurrency: true });
7004+
const TestModel = db.model('Test', fooSchema);
7005+
7006+
const errorDoc = await TestModel.create({ bar: 0 });
7007+
const okDoc = await TestModel.create({ bar: 0 });
7008+
7009+
// update 1
7010+
errorDoc.bar = 1;
7011+
await errorDoc.save();
7012+
7013+
// parallel update
7014+
const errorDocCopy = await TestModel.findById(errorDoc._id);
7015+
errorDocCopy.bar = 99;
7016+
await errorDocCopy.save();
7017+
7018+
errorDoc.bar = 2;
7019+
okDoc.bar = 2;
7020+
const err = await TestModel.bulkSave([errorDoc, okDoc]).then(() => null, err => err);
7021+
assert.equal(err.name, 'MongooseBulkSaveIncompleteError');
7022+
assert.equal(err.numDocumentsNotUpdated, 1);
7023+
7024+
const updatedOkDoc = await TestModel.findById(okDoc._id);
7025+
assert.equal(updatedOkDoc.bar, 2);
70007026
});
70017027
it('should error if there is a validation error', async function() {
70027028
const fooSchema = new mongoose.Schema({

0 commit comments

Comments
 (0)