Skip to content

Commit dd152c8

Browse files
committed
Merge branch 'master' of github.com:Automattic/mongoose
2 parents ed8bad0 + de49562 commit dd152c8

File tree

5 files changed

+107
-6
lines changed

5 files changed

+107
-6
lines changed

lib/cursor/aggregationCursor.js

+8
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,20 @@ util.inherits(AggregationCursor, Readable);
5757
function _init(model, c, agg) {
5858
if (!model.collection.buffer) {
5959
model.hooks.execPre('aggregate', agg, function() {
60+
if (typeof agg.options?.cursor?.transform === 'function') {
61+
c._transforms.push(agg.options.cursor.transform);
62+
}
63+
6064
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
6165
c.emit('cursor', c.cursor);
6266
});
6367
} else {
6468
model.collection.emitter.once('queue', function() {
6569
model.hooks.execPre('aggregate', agg, function() {
70+
if (typeof agg.options?.cursor?.transform === 'function') {
71+
c._transforms.push(agg.options.cursor.transform);
72+
}
73+
6674
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
6775
c.emit('cursor', c.cursor);
6876
});

lib/document.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -3901,14 +3901,24 @@ Document.prototype.$toObject = function(options, json) {
39013901
*
39023902
* _Note: if a transform function returns `undefined`, the return value will be ignored._
39033903
*
3904-
* Transformations may also be applied inline, overridding any transform set in the options:
3904+
* Transformations may also be applied inline, overridding any transform set in the schema options.
3905+
* Any transform function specified in `toObject` options also propagates to any subdocuments.
39053906
*
3906-
* function xform (doc, ret, options) {
3907-
* return { inline: ret.name, custom: true }
3907+
* function deleteId(doc, ret, options) {
3908+
* delete ret._id;
3909+
* return ret;
39083910
* }
39093911
*
3910-
* // pass the transform as an inline option
3911-
* doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
3912+
* const schema = mongoose.Schema({ name: String, docArr: [{ name: String }] });
3913+
* const TestModel = mongoose.model('Test', schema);
3914+
*
3915+
* const doc = new TestModel({ name: 'test', docArr: [{ name: 'test' }] });
3916+
*
3917+
* // pass the transform as an inline option. Deletes `_id` property
3918+
* // from both the top-level document and the subdocument.
3919+
* const obj = doc.toObject({ transform: deleteId });
3920+
* obj._id; // undefined
3921+
* obj.docArr[0]._id; // undefined
39123922
*
39133923
* If you want to skip transformations, use `transform: false`:
39143924
*

lib/plugins/trackTransaction.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function mergeAtomics(destination, source) {
8585
destination.$addToSet = (destination.$addToSet || []).concat(source.$addToSet);
8686
}
8787
if (source.$set != null) {
88-
destination.$set = Object.assign(destination.$set || {}, source.$set);
88+
destination.$set = Array.isArray(source.$set) ? [...source.$set] : Object.assign({}, source.$set);
8989
}
9090

9191
return destination;

test/aggregate.test.js

+27
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const start = require('./common');
88

99
const assert = require('assert');
10+
const stream = require('stream');
1011

1112
const Aggregate = require('../lib/aggregate');
1213

@@ -1215,6 +1216,32 @@ describe('aggregate: ', function() {
12151216
assert.equal(res[1].test, 'a test');
12161217
});
12171218

1219+
it('cursor supports transform option (gh-14331)', async function() {
1220+
const mySchema = new Schema({ name: String });
1221+
const Test = db.model('Test', mySchema);
1222+
1223+
await Test.deleteMany({});
1224+
await Test.create([{ name: 'Apple' }, { name: 'Apple' }]);
1225+
1226+
let resolve;
1227+
const waitForStream = new Promise(innerResolve => {
1228+
resolve = innerResolve;
1229+
});
1230+
const otherStream = new stream.Writable({
1231+
write(chunk, encoding, callback) {
1232+
resolve(chunk.toString());
1233+
callback();
1234+
}
1235+
});
1236+
1237+
await Test.
1238+
aggregate([{ $match: { name: 'Apple' } }]).
1239+
cursor({ transform: JSON.stringify }).
1240+
pipe(otherStream);
1241+
const streamValue = await waitForStream;
1242+
assert.ok(streamValue.includes('"name":"Apple"'), streamValue);
1243+
});
1244+
12181245
describe('Mongo 3.6 options', function() {
12191246
before(async function() {
12201247
await onlyTestAtOrAbove('3.6', this);

test/docs/transactions.test.js

+56
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,62 @@ describe('transactions', function() {
397397
assert.equal(docs[0].name, 'test');
398398
});
399399

400+
it('handles resetting array state with $set atomic (gh-13698)', async function() {
401+
db.deleteModel(/Test/);
402+
const subItemSchema = new mongoose.Schema(
403+
{
404+
name: { type: String, required: true }
405+
},
406+
{ _id: false }
407+
);
408+
409+
const itemSchema = new mongoose.Schema(
410+
{
411+
name: { type: String, required: true },
412+
subItems: { type: [subItemSchema], required: true }
413+
},
414+
{ _id: false }
415+
);
416+
417+
const schema = new mongoose.Schema({
418+
items: { type: [itemSchema], required: true }
419+
});
420+
421+
const Test = db.model('Test', schema);
422+
423+
const { _id } = await Test.create({
424+
items: [
425+
{ name: 'test1', subItems: [{ name: 'x1' }] },
426+
{ name: 'test2', subItems: [{ name: 'x2' }] }
427+
]
428+
});
429+
430+
const doc = await Test.findById(_id).orFail();
431+
let attempt = 0;
432+
433+
await db.transaction(async(session) => {
434+
await doc.save({ session });
435+
436+
if (attempt === 0) {
437+
attempt += 1;
438+
throw new mongoose.mongo.MongoServerError({
439+
message: 'Test transient transaction failures & retries',
440+
errorLabels: [mongoose.mongo.MongoErrorLabel.TransientTransactionError]
441+
});
442+
}
443+
});
444+
445+
const { items } = await Test.findById(_id).orFail();
446+
assert.ok(Array.isArray(items));
447+
assert.equal(items.length, 2);
448+
assert.equal(items[0].name, 'test1');
449+
assert.equal(items[0].subItems.length, 1);
450+
assert.equal(items[0].subItems[0].name, 'x1');
451+
assert.equal(items[1].name, 'test2');
452+
assert.equal(items[1].subItems.length, 1);
453+
assert.equal(items[1].subItems[0].name, 'x2');
454+
});
455+
400456
it('transaction() retains modified status for documents created outside of the transaction then modified inside the transaction (gh-13973)', async function() {
401457
db.deleteModel(/Test/);
402458
const Test = db.model('Test', Schema({ status: String }));

0 commit comments

Comments
 (0)