Skip to content
Open
10 changes: 9 additions & 1 deletion lib/helpers/setDefaultsOnInsert.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function(filter, schema, castedDoc, options) {
}

const keys = Object.keys(castedDoc || {});
const updatedKeys = {};
const updatedKeys = Object.create(null);
const updatedValues = {};
const numKeys = keys.length;

Expand Down Expand Up @@ -55,6 +55,14 @@ module.exports = function(filter, schema, castedDoc, options) {
}
}
updatedKeys[path] = true;
// Also mark all parent prefixes so child-path lookups are O(1).
// e.g. 'extraProps.location' also marks 'extraProps'
const pieces = path.split('.');
let cur = pieces[0];
for (let j = 1; j < pieces.length; ++j) {
updatedKeys[cur] = true;
cur += '.' + pieces[j];
}
}

if (options?.overwrite && !hasDollarUpdate) {
Expand Down
38 changes: 38 additions & 0 deletions test/model.findOneAndUpdate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,44 @@ describe('model: findOneAndUpdate:', function() {
assert.equal(1, count);
});

it('includes dot-notation filter paths in upserted document with sub-schema default (gh-16030)', async function() {
const addressSchema = new Schema({ city: String });
const userSchema = new Schema({
firstName: String,
lastName: String,
address: { type: addressSchema, default: {} }
});
const User = db.model('User', userSchema);

const foundUser = await User.findOneAndUpdate(
{ 'address.city': 'New York' },
{ $setOnInsert: { firstName: 'John' }, $set: { lastName: 'Smith' } },
{ upsert: true, new: true, setDefaultsOnInsert: true });

assert.equal(foundUser.lastName, 'Smith');
assert.equal(foundUser.firstName, 'John');
assert.equal(foundUser.address.city, 'New York');
});

it('applies sub-schema default on upsert when filter has no dot-notation path (gh-16030)', async function() {
const addressSchema = new Schema({ city: String });
const userSchema = new Schema({
firstName: String,
lastName: String,
address: { type: addressSchema, default: { city: 'Chicago' } }
});
const User = db.model('User', userSchema);

const foundUser = await User.findOneAndUpdate(
{ firstName: 'John' },
{ $set: { lastName: 'Smith' } },
{ upsert: true, new: true, setDefaultsOnInsert: true });

assert.equal(foundUser.lastName, 'Smith');
assert.equal(foundUser.firstName, 'John');
assert.equal(foundUser.address.city, 'Chicago');
});

it('skips setting defaults within maps (gh-7909)', async function() {
const socialMediaHandleSchema = Schema({ links: [String] });
const profileSchema = Schema({
Expand Down