Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Automattic/mongoose
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Feb 16, 2024
2 parents ed8bad0 + de49562 commit dd152c8
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 6 deletions.
8 changes: 8 additions & 0 deletions lib/cursor/aggregationCursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,20 @@ util.inherits(AggregationCursor, Readable);
function _init(model, c, agg) {
if (!model.collection.buffer) {
model.hooks.execPre('aggregate', agg, function() {
if (typeof agg.options?.cursor?.transform === 'function') {
c._transforms.push(agg.options.cursor.transform);
}

c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
c.emit('cursor', c.cursor);
});
} else {
model.collection.emitter.once('queue', function() {
model.hooks.execPre('aggregate', agg, function() {
if (typeof agg.options?.cursor?.transform === 'function') {
c._transforms.push(agg.options.cursor.transform);
}

c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
c.emit('cursor', c.cursor);
});
Expand Down
20 changes: 15 additions & 5 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3901,14 +3901,24 @@ Document.prototype.$toObject = function(options, json) {
*
* _Note: if a transform function returns `undefined`, the return value will be ignored._
*
* Transformations may also be applied inline, overridding any transform set in the options:
* Transformations may also be applied inline, overridding any transform set in the schema options.
* Any transform function specified in `toObject` options also propagates to any subdocuments.
*
* function xform (doc, ret, options) {
* return { inline: ret.name, custom: true }
* function deleteId(doc, ret, options) {
* delete ret._id;
* return ret;
* }
*
* // pass the transform as an inline option
* doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
* const schema = mongoose.Schema({ name: String, docArr: [{ name: String }] });
* const TestModel = mongoose.model('Test', schema);
*
* const doc = new TestModel({ name: 'test', docArr: [{ name: 'test' }] });
*
* // pass the transform as an inline option. Deletes `_id` property
* // from both the top-level document and the subdocument.
* const obj = doc.toObject({ transform: deleteId });
* obj._id; // undefined
* obj.docArr[0]._id; // undefined
*
* If you want to skip transformations, use `transform: false`:
*
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/trackTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function mergeAtomics(destination, source) {
destination.$addToSet = (destination.$addToSet || []).concat(source.$addToSet);
}
if (source.$set != null) {
destination.$set = Object.assign(destination.$set || {}, source.$set);
destination.$set = Array.isArray(source.$set) ? [...source.$set] : Object.assign({}, source.$set);
}

return destination;
Expand Down
27 changes: 27 additions & 0 deletions test/aggregate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const start = require('./common');

const assert = require('assert');
const stream = require('stream');

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

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

it('cursor supports transform option (gh-14331)', async function() {
const mySchema = new Schema({ name: String });
const Test = db.model('Test', mySchema);

await Test.deleteMany({});
await Test.create([{ name: 'Apple' }, { name: 'Apple' }]);

let resolve;
const waitForStream = new Promise(innerResolve => {
resolve = innerResolve;
});
const otherStream = new stream.Writable({
write(chunk, encoding, callback) {
resolve(chunk.toString());
callback();
}
});

await Test.
aggregate([{ $match: { name: 'Apple' } }]).
cursor({ transform: JSON.stringify }).
pipe(otherStream);
const streamValue = await waitForStream;
assert.ok(streamValue.includes('"name":"Apple"'), streamValue);
});

describe('Mongo 3.6 options', function() {
before(async function() {
await onlyTestAtOrAbove('3.6', this);
Expand Down
56 changes: 56 additions & 0 deletions test/docs/transactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,62 @@ describe('transactions', function() {
assert.equal(docs[0].name, 'test');
});

it('handles resetting array state with $set atomic (gh-13698)', async function() {
db.deleteModel(/Test/);
const subItemSchema = new mongoose.Schema(
{
name: { type: String, required: true }
},
{ _id: false }
);

const itemSchema = new mongoose.Schema(
{
name: { type: String, required: true },
subItems: { type: [subItemSchema], required: true }
},
{ _id: false }
);

const schema = new mongoose.Schema({
items: { type: [itemSchema], required: true }
});

const Test = db.model('Test', schema);

const { _id } = await Test.create({
items: [
{ name: 'test1', subItems: [{ name: 'x1' }] },
{ name: 'test2', subItems: [{ name: 'x2' }] }
]
});

const doc = await Test.findById(_id).orFail();
let attempt = 0;

await db.transaction(async(session) => {
await doc.save({ session });

if (attempt === 0) {
attempt += 1;
throw new mongoose.mongo.MongoServerError({
message: 'Test transient transaction failures & retries',
errorLabels: [mongoose.mongo.MongoErrorLabel.TransientTransactionError]
});
}
});

const { items } = await Test.findById(_id).orFail();
assert.ok(Array.isArray(items));
assert.equal(items.length, 2);
assert.equal(items[0].name, 'test1');
assert.equal(items[0].subItems.length, 1);
assert.equal(items[0].subItems[0].name, 'x1');
assert.equal(items[1].name, 'test2');
assert.equal(items[1].subItems.length, 1);
assert.equal(items[1].subItems[0].name, 'x2');
});

it('transaction() retains modified status for documents created outside of the transaction then modified inside the transaction (gh-13973)', async function() {
db.deleteModel(/Test/);
const Test = db.model('Test', Schema({ status: String }));
Expand Down

0 comments on commit dd152c8

Please sign in to comment.