Skip to content

Commit 9dc416a

Browse files
authored
Merge pull request #15266 from Automattic/vkarpov15/gh-15265
fix(model+connection): return MongoDB BulkWriteResult instance even if no valid ops
2 parents 384b8e8 + 633ea9d commit 9dc416a

File tree

9 files changed

+93
-66
lines changed

9 files changed

+93
-66
lines changed

lib/connection.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const CreateCollectionsError = require('./error/createCollectionsError');
2323
const castBulkWrite = require('./helpers/model/castBulkWrite');
2424
const { modelSymbol } = require('./helpers/symbols');
2525
const isPromise = require('./helpers/isPromise');
26+
const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
2627

2728
const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
2829
const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
@@ -559,24 +560,27 @@ Connection.prototype.bulkWrite = async function bulkWrite(ops, options) {
559560
'bulkWrite'
560561
);
561562
}
562-
return getDefaultBulkwriteResult();
563+
const BulkWriteResult = this.base.driver.get().BulkWriteResult;
564+
const res = new BulkWriteResult(getDefaultBulkwriteResult(), false);
565+
return decorateBulkWriteResult(res, validationErrors, results);
563566
}
564567

565568
let error;
566569
[res, error] = await this.client.bulkWrite(validOps, options).
567570
then(res => ([res, null])).
568571
catch(err => ([null, err]));
569572

573+
for (let i = 0; i < validOpIndexes.length; ++i) {
574+
results[validOpIndexes[i]] = null;
575+
}
570576
if (error) {
571577
if (validationErrors.length > 0) {
578+
decorateBulkWriteResult(error, validationErrors, results);
572579
error.mongoose = error.mongoose || {};
573580
error.mongoose.validationErrors = validationErrors;
574581
}
575582
}
576583

577-
for (let i = 0; i < validOpIndexes.length; ++i) {
578-
results[validOpIndexes[i]] = null;
579-
}
580584
if (validationErrors.length > 0) {
581585
if (options.throwOnValidationError) {
582586
throw new MongooseBulkWriteError(
@@ -586,9 +590,7 @@ Connection.prototype.bulkWrite = async function bulkWrite(ops, options) {
586590
'bulkWrite'
587591
);
588592
} else {
589-
res.mongoose = res.mongoose || {};
590-
res.mongoose.validationErrors = validationErrors;
591-
res.mongoose.results = results;
593+
decorateBulkWriteResult(res, validationErrors, results);
592594
}
593595
}
594596
}

lib/drivers/browser/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ exports.Collection = function() {
1010
exports.Connection = function() {
1111
throw new Error('Cannot create a connection from browser library');
1212
};
13+
exports.BulkWriteResult = function() {};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
const BulkWriteResult = require('mongodb/lib/bulk/common').BulkWriteResult;
4+
5+
module.exports = BulkWriteResult;

lib/drivers/node-mongodb-native/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44

55
'use strict';
66

7+
exports.BulkWriteResult = require('./bulkWriteResult');
78
exports.Collection = require('./collection');
89
exports.Connection = require('./connection');

lib/helpers/getDefaultBulkwriteResult.js

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
'use strict';
2+
23
function getDefaultBulkwriteResult() {
34
return {
4-
result: {
5-
ok: 1,
6-
writeErrors: [],
7-
writeConcernErrors: [],
8-
insertedIds: [],
9-
nInserted: 0,
10-
nUpserted: 0,
11-
nMatched: 0,
12-
nModified: 0,
13-
nRemoved: 0,
14-
upserted: []
15-
},
16-
insertedCount: 0,
17-
matchedCount: 0,
18-
modifiedCount: 0,
19-
deletedCount: 0,
20-
upsertedCount: 0,
21-
upsertedIds: {},
22-
insertedIds: {},
23-
n: 0
5+
ok: 1,
6+
nInserted: 0,
7+
nUpserted: 0,
8+
nMatched: 0,
9+
nModified: 0,
10+
nRemoved: 0,
11+
upserted: [],
12+
writeErrors: [],
13+
insertedIds: [],
14+
writeConcernErrors: []
2415
};
2516
}
2617

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = function decorateBulkWriteResult(resultOrError, validationErrors, results) {
4+
resultOrError.mongoose = resultOrError.mongoose || {};
5+
resultOrError.mongoose.validationErrors = validationErrors;
6+
resultOrError.mongoose.results = results;
7+
return resultOrError;
8+
};

lib/model.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const Document = require('./document');
1010
const DocumentNotFoundError = require('./error/notFound');
1111
const EventEmitter = require('events').EventEmitter;
1212
const Kareem = require('kareem');
13-
const { MongoBulkWriteError } = require('mongodb');
1413
const MongooseBulkWriteError = require('./error/bulkWriteError');
1514
const MongooseError = require('./error/index');
1615
const ObjectParameterError = require('./error/objectParameter');
@@ -69,6 +68,7 @@ const utils = require('./utils');
6968
const minimize = require('./helpers/minimize');
7069
const MongooseBulkSaveIncompleteError = require('./error/bulkSaveIncompleteError');
7170
const ObjectExpectedError = require('./error/objectExpected');
71+
const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
7272

7373
const modelCollectionSymbol = Symbol('mongoose#Model#collection');
7474
const modelDbSymbol = Symbol('mongoose#Model#db');
@@ -3399,7 +3399,11 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
33993399
const ordered = options.ordered == null ? true : options.ordered;
34003400

34013401
if (ops.length === 0) {
3402-
return getDefaultBulkwriteResult();
3402+
const BulkWriteResult = this.base.driver.get().BulkWriteResult;
3403+
const bulkWriteResult = new BulkWriteResult(getDefaultBulkwriteResult(), false);
3404+
bulkWriteResult.n = 0;
3405+
decorateBulkWriteResult(bulkWriteResult, [], []);
3406+
return bulkWriteResult;
34033407
}
34043408

34053409
const validations = ops.map(op => castBulkWrite(this, op, options));
@@ -3470,18 +3474,24 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
34703474
'bulkWrite'
34713475
);
34723476
}
3473-
return getDefaultBulkwriteResult();
3477+
const BulkWriteResult = this.base.driver.get().BulkWriteResult;
3478+
const bulkWriteResult = new BulkWriteResult(getDefaultBulkwriteResult(), false);
3479+
bulkWriteResult.result = getDefaultBulkwriteResult();
3480+
decorateBulkWriteResult(bulkWriteResult, validationErrors, results);
3481+
return bulkWriteResult;
34743482
}
34753483

34763484
let error;
34773485
[res, error] = await this.$__collection.bulkWrite(validOps, options).
34783486
then(res => ([res, null])).
34793487
catch(error => ([null, error]));
34803488

3489+
for (let i = 0; i < validOpIndexes.length; ++i) {
3490+
results[validOpIndexes[i]] = null;
3491+
}
34813492
if (error) {
34823493
if (validationErrors.length > 0) {
3483-
error.mongoose = error.mongoose || {};
3484-
error.mongoose.validationErrors = validationErrors;
3494+
decorateBulkWriteResult(error, validationErrors, results);
34853495
}
34863496

34873497
await new Promise((resolve, reject) => {
@@ -3495,9 +3505,6 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
34953505
});
34963506
}
34973507

3498-
for (let i = 0; i < validOpIndexes.length; ++i) {
3499-
results[validOpIndexes[i]] = null;
3500-
}
35013508
if (validationErrors.length > 0) {
35023509
if (options.throwOnValidationError) {
35033510
throw new MongooseBulkWriteError(
@@ -3507,9 +3514,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
35073514
'bulkWrite'
35083515
);
35093516
} else {
3510-
res.mongoose = res.mongoose || {};
3511-
res.mongoose.validationErrors = validationErrors;
3512-
res.mongoose.results = results;
3517+
decorateBulkWriteResult(res, validationErrors, results);
35133518
}
35143519
}
35153520
}
@@ -3575,7 +3580,7 @@ Model.bulkSave = async function bulkSave(documents, options) {
35753580
(err) => ({ bulkWriteResult: null, bulkWriteError: err })
35763581
);
35773582
// If not a MongoBulkWriteError, treat this as all documents failed to save.
3578-
if (bulkWriteError != null && !(bulkWriteError instanceof MongoBulkWriteError)) {
3583+
if (bulkWriteError != null && bulkWriteError.name !== 'MongoBulkWriteError') {
35793584
throw bulkWriteError;
35803585
}
35813586

test/model.test.js

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4121,7 +4121,7 @@ describe('Model', function() {
41214121
{ ordered: false, throwOnValidationError: true }
41224122
).then(() => null, err => err);
41234123
assert.ok(err);
4124-
assert.equal(err.name, 'MongooseBulkWriteError');
4124+
assert.equal(err.name, 'MongooseBulkWriteError', err.stack);
41254125
assert.equal(err.validationErrors[0].errors['num'].name, 'CastError');
41264126
});
41274127

@@ -6492,17 +6492,9 @@ describe('Model', function() {
64926492
assert.deepEqual(
64936493
res,
64946494
{
6495-
result: {
6496-
ok: 1,
6497-
writeErrors: [],
6498-
writeConcernErrors: [],
6499-
insertedIds: [],
6500-
nInserted: 0,
6501-
nUpserted: 0,
6502-
nMatched: 0,
6503-
nModified: 0,
6504-
nRemoved: 0,
6505-
upserted: []
6495+
mongoose: {
6496+
results: [],
6497+
validationErrors: []
65066498
},
65076499
insertedCount: 0,
65086500
matchedCount: 0,
@@ -6514,7 +6506,20 @@ describe('Model', function() {
65146506
n: 0
65156507
}
65166508
);
6517-
6509+
assert.deepEqual(res.result, {
6510+
ok: 1,
6511+
writeErrors: [],
6512+
writeConcernErrors: [],
6513+
insertedIds: [],
6514+
nInserted: 0,
6515+
nUpserted: 0,
6516+
nMatched: 0,
6517+
nModified: 0,
6518+
nRemoved: 0,
6519+
upserted: []
6520+
});
6521+
6522+
assert.equal(typeof res.getWriteErrorAt, 'function');
65186523
});
65196524

65206525
it('Model.bulkWrite(...) does not throw an error with upsert:true, setDefaultsOnInsert: true (gh-9157)', async function() {
@@ -6554,28 +6559,37 @@ describe('Model', function() {
65546559
assert.deepEqual(
65556560
res,
65566561
{
6557-
result: {
6558-
ok: 1,
6559-
writeErrors: [],
6560-
writeConcernErrors: [],
6561-
insertedIds: [],
6562-
nInserted: 0,
6563-
nUpserted: 0,
6564-
nMatched: 0,
6565-
nModified: 0,
6566-
nRemoved: 0,
6567-
upserted: []
6568-
},
65696562
insertedCount: 0,
65706563
matchedCount: 0,
65716564
modifiedCount: 0,
65726565
deletedCount: 0,
65736566
upsertedCount: 0,
65746567
upsertedIds: {},
65756568
insertedIds: {},
6576-
n: 0
6569+
n: 0,
6570+
mongoose: {
6571+
results: [],
6572+
validationErrors: []
6573+
}
65776574
}
65786575
);
6576+
assert.deepEqual(
6577+
res.result,
6578+
{
6579+
ok: 1,
6580+
writeErrors: [],
6581+
writeConcernErrors: [],
6582+
insertedIds: [],
6583+
nInserted: 0,
6584+
nUpserted: 0,
6585+
nMatched: 0,
6586+
nModified: 0,
6587+
nRemoved: 0,
6588+
upserted: []
6589+
}
6590+
);
6591+
6592+
assert.equal(typeof res.getWriteErrorAt, 'function');
65796593
});
65806594

65816595
it('allows calling `create()` after `bulkWrite()` (gh-9350)', async function() {

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[] } }>;
311+
): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[], results: Array<Error | 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)