Skip to content

Commit 6145cca

Browse files
fix(model): make overwriteImmutable work on createdAt regardless of timestamps value
1 parent b7c1166 commit 6145cca

File tree

3 files changed

+87
-29
lines changed

3 files changed

+87
-29
lines changed

lib/helpers/model/castBulkWrite.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
119119
const createdAt = model.schema.$timestamps.createdAt;
120120
const updatedAt = model.schema.$timestamps.updatedAt;
121121
applyTimestampsToUpdate(now, createdAt, updatedAt, update, {
122-
timestamps: updateOne.timestamps
122+
timestamps: updateOne.timestamps,
123+
overwriteImmutable: updateOne.overwriteImmutable
123124
});
124125
}
125126

@@ -189,7 +190,8 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
189190
const createdAt = model.schema.$timestamps.createdAt;
190191
const updatedAt = model.schema.$timestamps.updatedAt;
191192
applyTimestampsToUpdate(now, createdAt, updatedAt, updateMany['update'], {
192-
timestamps: updateMany.timestamps
193+
timestamps: updateMany.timestamps,
194+
overwriteImmutable: updateMany.overwriteImmutable
193195
});
194196
}
195197
if (doInitTimestamps) {

lib/helpers/update/applyTimestampsToUpdate.js

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,33 +80,46 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
8080
}
8181

8282
if (!skipCreatedAt && createdAt) {
83-
if (currentUpdate[createdAt]) {
84-
delete currentUpdate[createdAt];
85-
}
86-
if (currentUpdate.$set && currentUpdate.$set[createdAt]) {
87-
delete currentUpdate.$set[createdAt];
88-
}
89-
let timestampSet = false;
90-
if (createdAt.indexOf('.') !== -1) {
91-
const pieces = createdAt.split('.');
92-
for (let i = 1; i < pieces.length; ++i) {
93-
const remnant = pieces.slice(-i).join('.');
94-
const start = pieces.slice(0, -i).join('.');
95-
if (currentUpdate[start] != null) {
96-
currentUpdate[start][remnant] = now;
97-
timestampSet = true;
98-
break;
99-
} else if (currentUpdate.$set && currentUpdate.$set[start]) {
100-
currentUpdate.$set[start][remnant] = now;
101-
timestampSet = true;
102-
break;
83+
const overwriteImmutable = get(options, 'overwriteImmutable', false);
84+
const hasUserCreatedAt = currentUpdate[createdAt] != null || currentUpdate?.$set[createdAt] != null;
85+
86+
// If overwriteImmutable is true and user provided createdAt, keep their value
87+
if (overwriteImmutable && hasUserCreatedAt) {
88+
// Move createdAt from top-level to $set if needed
89+
if (currentUpdate[createdAt] != null) {
90+
updates.$set[createdAt] = currentUpdate[createdAt];
91+
delete currentUpdate[createdAt];
92+
}
93+
// User's value is already in $set, nothing more to do
94+
} else {
95+
if (currentUpdate[createdAt]) {
96+
delete currentUpdate[createdAt];
97+
}
98+
if (currentUpdate.$set && currentUpdate.$set[createdAt]) {
99+
delete currentUpdate.$set[createdAt];
100+
}
101+
let timestampSet = false;
102+
if (createdAt.indexOf('.') !== -1) {
103+
const pieces = createdAt.split('.');
104+
for (let i = 1; i < pieces.length; ++i) {
105+
const remnant = pieces.slice(-i).join('.');
106+
const start = pieces.slice(0, -i).join('.');
107+
if (currentUpdate[start] != null) {
108+
currentUpdate[start][remnant] = now;
109+
timestampSet = true;
110+
break;
111+
} else if (currentUpdate.$set && currentUpdate.$set[start]) {
112+
currentUpdate.$set[start][remnant] = now;
113+
timestampSet = true;
114+
break;
115+
}
103116
}
104117
}
105-
}
106118

107-
if (!timestampSet) {
108-
updates.$setOnInsert = updates.$setOnInsert || {};
109-
updates.$setOnInsert[createdAt] = now;
119+
if (!timestampSet) {
120+
updates.$setOnInsert = updates.$setOnInsert || {};
121+
updates.$setOnInsert[createdAt] = now;
122+
}
110123
}
111124
}
112125

test/model.updateOne.test.js

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,45 +2685,88 @@ describe('model: updateOne: ', function() {
26852685
// Arrange
26862686
const { User } = createTestContext();
26872687
const user = await User.create({ name: 'John', ssn: '123-45-6789' });
2688+
const customCreatedAt = new Date('2020-01-01');
26882689

26892690
// Act
26902691
await User.bulkWrite([{
26912692
updateOne: {
26922693
filter: { _id: user._id },
2693-
update: { ssn: '999-99-9999' },
2694+
update: { createdAt: customCreatedAt, ssn: '999-99-9999' },
26942695
overwriteImmutable: true
26952696
}
26962697
}]);
26972698

26982699
// Assert
26992700
const updatedUser = await User.findById(user._id);
27002701
assert.strictEqual(updatedUser.ssn, '999-99-9999');
2702+
assert.strictEqual(updatedUser.createdAt.valueOf(), customCreatedAt.valueOf());
27012703
});
27022704

27032705
it('updateMany can update immutable field with overwriteImmutable: true', async function() {
27042706
// Arrange
27052707
const { User } = createTestContext();
27062708
const user = await User.create({ name: 'Alice', ssn: '111-11-1111' });
2709+
const customCreatedAt = new Date('2020-01-01');
27072710

27082711
// Act
27092712
await User.bulkWrite([{
27102713
updateMany: {
27112714
filter: { _id: user._id },
2712-
update: { ssn: '000-00-0000' },
2715+
update: { createdAt: customCreatedAt, ssn: '000-00-0000' },
27132716
overwriteImmutable: true
27142717
}
27152718
}]);
27162719

27172720
// Assert
27182721
const updatedUser = await User.findById(user._id);
27192722
assert.strictEqual(updatedUser.ssn, '000-00-0000');
2723+
assert.strictEqual(updatedUser.createdAt.valueOf(), customCreatedAt.valueOf());
27202724
});
27212725

2726+
for (const timestamps of [true, false, null, undefined])
2727+
it(`overwriting immutable createdAt with bulkWrite (gh-15781) when \`timestamps\` is \`${timestamps}\``, async function() {
2728+
// Arrange
2729+
const schema = Schema({ name: String }, { timestamps: true });
2730+
2731+
const Model = db.model('Test', schema);
2732+
2733+
const doc1 = await Model.create({ name: 'gh-15781-1' });
2734+
const doc2 = await Model.create({ name: 'gh-15781-2' });
2735+
2736+
// Act
2737+
const createdAt = new Date('2011-06-01');
2738+
2739+
await Model.bulkWrite([
2740+
{
2741+
updateOne: {
2742+
filter: { _id: doc1._id },
2743+
update: { createdAt },
2744+
overwriteImmutable: true,
2745+
timestamps
2746+
}
2747+
},
2748+
{
2749+
updateMany: {
2750+
filter: { _id: doc2._id },
2751+
update: { createdAt },
2752+
overwriteImmutable: true,
2753+
timestamps
2754+
}
2755+
}
2756+
]);
2757+
2758+
// Assert
2759+
const updatesDocs = await Model.find({ _id: { $in: [doc1._id, doc2._id] } });
2760+
2761+
assert.equal(updatesDocs[0].createdAt.valueOf(), createdAt.valueOf());
2762+
assert.equal(updatesDocs[1].createdAt.valueOf(), createdAt.valueOf());
2763+
});
2764+
27222765
function createTestContext() {
27232766
const userSchema = new Schema({
27242767
name: String,
27252768
ssn: { type: String, immutable: true }
2726-
});
2769+
}, { timestamps: true });
27272770
const User = db.model('User', userSchema);
27282771
return { User };
27292772
}

0 commit comments

Comments
 (0)