Skip to content

Commit

Permalink
fix(plugins): make trackTransaction avoid adding values to overwritte…
Browse files Browse the repository at this point in the history
…n array when transaction fails

Fix #14340
  • Loading branch information
vkarpov15 committed Feb 12, 2024
1 parent 4261e9f commit 54e52b0
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 6 deletions.
7 changes: 1 addition & 6 deletions lib/plugins/trackTransaction.js
Expand Up @@ -85,12 +85,7 @@ function mergeAtomics(destination, source) {
destination.$addToSet = (destination.$addToSet || []).concat(source.$addToSet);
}
if (source.$set != null) {
if (Array.isArray(destination.$set || []) && Array.isArray(source.$set || [])) {
destination.$set = (destination.$set || []).concat(source.$set);
} else {
destination.$set = Object.assign(destination.$set || {}, source.$set);
}

destination.$set = Array.isArray(source.$set) ? [...source.$set] : Object.assign({}, source.$set);
}

return destination;
Expand Down
57 changes: 57 additions & 0 deletions test/docs/transactions.test.js
Expand Up @@ -397,6 +397,63 @@ 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 });

// This is the important bit. Uncomment this to trigger a transaction retry.
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 54e52b0

Please sign in to comment.