Skip to content

Commit 748caea

Browse files
committed
feat(populate): introduce populate ordered option
1 parent 6107403 commit 748caea

File tree

3 files changed

+52
-12
lines changed

3 files changed

+52
-12
lines changed

lib/document.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4525,15 +4525,21 @@ Document.prototype.populate = async function populate() {
45254525
throw new MongooseError('Document.prototype.populate() no longer accepts a callback');
45264526
}
45274527

4528+
let ordered = null;
45284529
if (args.length !== 0) {
45294530
// use hash to remove duplicate paths
45304531
const res = utils.populate.apply(null, args);
4532+
ordered = res.ordered;
45314533
for (const populateOptions of res) {
45324534
pop[populateOptions.path] = populateOptions;
45334535
}
45344536
}
45354537

45364538
const paths = utils.object.vals(pop);
4539+
if (ordered) {
4540+
paths.ordered = ordered;
4541+
}
4542+
45374543
let topLevelModel = this.constructor;
45384544
if (this.$__isNested) {
45394545
topLevelModel = this.$__[scopeSymbol].constructor;

lib/model.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4275,11 +4275,20 @@ Model.populate = async function populate(docs, paths) {
42754275
}
42764276

42774277
// each path has its own query options and must be executed separately
4278-
const promises = [];
4279-
for (const path of paths) {
4280-
promises.push(_populatePath(this, docs, path));
4278+
if (paths.ordered) {
4279+
// Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
4280+
// one transaction in parallel.
4281+
for (const path of paths) {
4282+
await _populatePath(this, docs, path);
4283+
}
4284+
} else {
4285+
// By default, populate in parallel
4286+
const promises = [];
4287+
for (const path of paths) {
4288+
promises.push(_populatePath(this, docs, path));
4289+
}
4290+
await Promise.all(promises);
42814291
}
4282-
await Promise.all(promises);
42834292

42844293
return docs;
42854294
};
@@ -4399,12 +4408,22 @@ async function _populatePath(model, docs, populateOptions) {
43994408
return;
44004409
}
44014410

4402-
const promises = [];
4403-
for (const arr of params) {
4404-
promises.push(_execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); }));
4411+
if (populateOptions.ordered) {
4412+
// Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
4413+
// one transaction in parallel.
4414+
for (const arr of params) {
4415+
await _execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); });
4416+
}
4417+
} else {
4418+
// By default, populate in parallel
4419+
const promises = [];
4420+
for (const arr of params) {
4421+
promises.push(_execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); }));
4422+
}
4423+
4424+
await Promise.all(promises);
44054425
}
44064426

4407-
await Promise.all(promises);
44084427

44094428
for (const arr of params) {
44104429
const mod = arr[0];

lib/utils.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,11 @@ exports.populate = function populate(path, select, model, match, options, subPop
523523

524524
if (Array.isArray(path)) {
525525
const singles = makeSingles(path);
526-
return singles.map(o => exports.populate(o)[0]);
526+
const ret = singles.map(o => exports.populate(o)[0]);
527+
if (path.ordered) {
528+
ret.ordered = path.ordered;
529+
}
530+
return ret;
527531
}
528532

529533
if (exports.isObject(path)) {
@@ -551,8 +555,8 @@ exports.populate = function populate(path, select, model, match, options, subPop
551555
};
552556
}
553557

554-
if (typeof obj.path !== 'string') {
555-
throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
558+
if (typeof obj.path !== 'string' && !(Array.isArray(obj.path) && obj.path.every(el => typeof el === 'string'))) {
559+
throw new TypeError('utils.populate: invalid path. Expected string or array of strings. Got typeof `' + typeof path + '`');
556560
}
557561

558562
return _populateObj(obj);
@@ -600,7 +604,11 @@ function _populateObj(obj) {
600604
}
601605

602606
const ret = [];
603-
const paths = oneSpaceRE.test(obj.path) ? obj.path.split(manySpaceRE) : [obj.path];
607+
const paths = oneSpaceRE.test(obj.path)
608+
? obj.path.split(manySpaceRE)
609+
: Array.isArray(obj.path)
610+
? obj.path
611+
: [obj.path];
604612
if (obj.options != null) {
605613
obj.options = clone(obj.options);
606614
}
@@ -609,6 +617,13 @@ function _populateObj(obj) {
609617
ret.push(new PopulateOptions(Object.assign({}, obj, { path: path })));
610618
}
611619

620+
if (obj.ordered) {
621+
ret.ordered = true;
622+
for (const path of ret) {
623+
path.ordered = true;
624+
}
625+
}
626+
612627
return ret;
613628
}
614629

0 commit comments

Comments
 (0)