Skip to content

Commit f923f6c

Browse files
authored
Merge pull request #13989 from Automattic/vkarpov15/gh-13578
Remove `overwrite` option for `updateOne()`, `findOneAndUpdate()`, etc.
2 parents b630afb + 30888e3 commit f923f6c

File tree

12 files changed

+28
-160
lines changed

12 files changed

+28
-160
lines changed

docs/migrating_to_8.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ If you're still on Mongoose 6.x or earlier, please read the [Mongoose 6.x to 7.x
2020
* [`null` is valid for non-required string enums](#null-is-valid-for-non-required-string-enums)
2121
* [Apply minimize when `save()` updates an existing document](#apply-minimize-when-save-updates-an-existing-document)
2222
* [Apply base schema paths before discriminator paths](#apply-base-schema-paths-before-discriminator-paths)
23+
* [Removed `overwrite` option for `findOneAndUpdate()`](#removed-overwrite-option-for-findoneandupdate)
2324
* [Changed behavior for `findOneAndUpdate()` with `orFail()` and upsert](#changed-behavior-for-findoneandupdate-with-orfail-and-upsert)
2425
* [`create()` waits until all saves are done before throwing any error](#create-waits-until-all-saves-are-done-before-throwing-any-error)
2526
* [`Model.validate()` returns copy of object](#model-validate-returns-copy-of-object)
@@ -133,8 +134,8 @@ rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7
133134

134135
<h2 id="apply-base-schema-paths-before-discriminator-paths"><a href="#apply-base-schema-paths-before-discriminator-paths">Apply base schema paths before discriminator paths</a></h2>
135136

136-
This means that, in Mongoose 8, getters and setters on discriminator paths run _after_ getters and setters on base paths.
137-
In Mongoose 7, getters and setters on discriminator paths ran _before_ getters and setters on base paths.
137+
This means that, in Mongoose 8, getters and setters on discriminator paths run *after* getters and setters on base paths.
138+
In Mongoose 7, getters and setters on discriminator paths ran *before* getters and setters on base paths.
138139

139140
```javascript
140141

@@ -165,6 +166,15 @@ const doc = new D({ name: 'test', otherProp: 'test' });
165166
console.log(doc.toObject({ getters: true }));
166167
```
167168

169+
<h2 id="removed-overwrite-option-for-findoneandupdate"><a href="#removed-overwrite-option-for-findoneandupdate">Removed <code>overwrite</code> option for <code>findOneAndUpdate()</code></a></h2>
170+
171+
Mongoose 7 and earlier supported an `overwrite` option for `findOneAndUpdate()`, `updateOne()`, and `update()`.
172+
Before Mongoose 7, `overwrite` would skip wrapping the `update` parameter in `$set`, which meant that `findOneAndUpdate()` and `update()` would overwrite the matched document.
173+
In Mongoose 7, setting `overwrite` would convert `findOneAndUpdate()` to `findOneAndReplace()` and `updateOne()` to `replaceOne()` to retain backwards compatibility.
174+
175+
In Mongoose 8, the `overwrite` option is no longer supported.
176+
If you want to overwrite the entire document, use `findOneAndReplace()` or `replaceOne()`.
177+
168178
<h2 id="changed-behavior-for-findoneandupdate-with-orfail-and-upsert"><a href="#changed-behavior-for-findoneandupdate-with-orfail-and-upsert">Changed behavior for <code>findOneAndUpdate()</code> with <code>orFail()</code> and upsert</a></h2>
169179

170180
In Mongoose 7, `findOneAndUpdate(filter, update, { upsert: true }).orFail()` would throw a `DocumentNotFoundError` if a new document was upserted.
@@ -253,4 +263,4 @@ const schema = new Schema<User>({
253263

254264
// Works in Mongoose 8. Compile error in Mongoose 7.
255265
const names: string[] = await MyModel.distinct('name');
256-
```
266+
```

lib/helpers/model/castBulkWrite.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ module.exports = function castBulkWrite(originalModel, op, options) {
9999

100100
op['updateOne']['update'] = castUpdate(model.schema, op['updateOne']['update'], {
101101
strict: strict,
102-
overwrite: false,
103102
upsert: op['updateOne'].upsert
104103
}, model, op['updateOne']['filter']);
105104
} catch (error) {
@@ -157,7 +156,6 @@ module.exports = function castBulkWrite(originalModel, op, options) {
157156

158157
op['updateMany']['update'] = castUpdate(model.schema, op['updateMany']['update'], {
159158
strict: strict,
160-
overwrite: false,
161159
upsert: op['updateMany'].upsert
162160
}, model, op['updateMany']['filter']);
163161
} catch (error) {

lib/helpers/query/castUpdate.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const mongodbUpdateOperators = new Set([
3838
* @param {Schema} schema
3939
* @param {Object} obj
4040
* @param {Object} [options]
41-
* @param {Boolean} [options.overwrite] defaults to false
4241
* @param {Boolean|String} [options.strict] defaults to true
4342
* @param {Query} context passed to setters
4443
* @return {Boolean} true iff the update is non-empty
@@ -61,7 +60,7 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
6160
return obj;
6261
}
6362

64-
if (options.upsert && !options.overwrite) {
63+
if (options.upsert) {
6564
moveImmutableProperties(schema, obj, context);
6665
}
6766

@@ -70,13 +69,11 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
7069
const ret = {};
7170
let val;
7271
let hasDollarKey = false;
73-
const overwrite = options.overwrite;
7472

7573
filter = filter || {};
7674
while (i--) {
7775
const op = ops[i];
78-
// if overwrite is set, don't do any of the special $set stuff
79-
if (!mongodbUpdateOperators.has(op) && !overwrite) {
76+
if (!mongodbUpdateOperators.has(op)) {
8077
// fix up $set sugar
8178
if (!ret.$set) {
8279
if (obj.$set) {
@@ -106,10 +103,8 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
106103
if (val &&
107104
typeof val === 'object' &&
108105
!Buffer.isBuffer(val) &&
109-
(!overwrite || mongodbUpdateOperators.has(op))) {
106+
mongodbUpdateOperators.has(op)) {
110107
walkUpdatePath(schema, val, op, options, context, filter);
111-
} else if (overwrite && ret && typeof ret === 'object') {
112-
walkUpdatePath(schema, ret, '$set', options, context, filter);
113108
} else {
114109
const msg = 'Invalid atomic update value for ' + op + '. '
115110
+ 'Expected an object, received ' + typeof val;
@@ -239,7 +234,6 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
239234
}
240235

241236
if (op !== '$setOnInsert' &&
242-
!options.overwrite &&
243237
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
244238
continue;
245239
}
@@ -334,7 +328,6 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
334328

335329
// You can use `$setOnInsert` with immutable keys
336330
if (op !== '$setOnInsert' &&
337-
!options.overwrite &&
338331
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
339332
continue;
340333
}

lib/helpers/timestamps/setupTimestamps.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
102102
updatedAt,
103103
this.getUpdate(),
104104
this._mongooseOptions,
105-
this.schema
105+
replaceOps.has(this.op)
106106
);
107107
applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
108108
next();

lib/helpers/update/applyTimestampsToUpdate.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ module.exports = applyTimestampsToUpdate;
1212
* ignore
1313
*/
1414

15-
function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options) {
15+
function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options, isReplace) {
1616
const updates = currentUpdate;
1717
let _updates = updates;
18-
const overwrite = get(options, 'overwrite', false);
1918
const timestamps = get(options, 'timestamps', true);
2019

2120
// Support skipping timestamps at the query level, see gh-6980
@@ -26,7 +25,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
2625
const skipCreatedAt = timestamps != null && timestamps.createdAt === false;
2726
const skipUpdatedAt = timestamps != null && timestamps.updatedAt === false;
2827

29-
if (overwrite) {
28+
if (isReplace) {
3029
if (currentUpdate && currentUpdate.$set) {
3130
currentUpdate = currentUpdate.$set;
3231
updates.$set = {};

lib/model.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,7 +2394,6 @@ Model.$where = function $where() {
23942394
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
23952395
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
23962396
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
2397-
* @param {Boolean} [options.overwrite=false] If set to `true`, Mongoose will convert this `findOneAndUpdate()` to a `findOneAndReplace()`. This option is deprecated and only supported for backwards compatiblity.
23982397
* @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
23992398
* @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
24002399
* @param {Boolean} [options.new=false] if true, return the modified document rather than the original
@@ -2469,9 +2468,6 @@ Model.findOneAndUpdate = function(conditions, update, options) {
24692468
* // is sent as
24702469
* Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options)
24712470
*
2472-
* This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`.
2473-
* To prevent this behaviour, see the `overwrite` option
2474-
*
24752471
* #### Note:
24762472
*
24772473
* `findOneAndX` and `findByIdAndX` functions support limited validation. You can
@@ -2492,7 +2488,6 @@ Model.findOneAndUpdate = function(conditions, update, options) {
24922488
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
24932489
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
24942490
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
2495-
* @param {Boolean} [options.overwrite=false] If set to `true`, Mongoose will convert this `findByIdAndUpdate()` to a `findByIdAndReplace()`. This option is deprecated and only supported for backwards compatiblity.
24962491
* @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
24972492
* @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
24982493
* @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created

lib/query.js

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const castUpdate = require('./helpers/query/castUpdate');
2121
const clone = require('./helpers/clone');
2222
const completeMany = require('./helpers/query/completeMany');
2323
const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
24-
const hasDollarKeys = require('./helpers/query/hasDollarKeys');
2524
const helpers = require('./queryHelpers');
2625
const immediate = require('./helpers/immediate');
2726
const internalToObjectOptions = require('./options').internalToObjectOptions;
@@ -1619,10 +1618,6 @@ Query.prototype.setOptions = function(options, overwrite) {
16191618
this._mongooseOptions.sanitizeFilter = options.sanitizeFilter;
16201619
delete options.sanitizeFilter;
16211620
}
1622-
if ('overwrite' in options) {
1623-
this._mongooseOptions.overwrite = options.overwrite;
1624-
delete options.overwrite;
1625-
}
16261621
if ('timestamps' in options) {
16271622
this._mongooseOptions.timestamps = options.timestamps;
16281623
delete options.timestamps;
@@ -1670,15 +1665,6 @@ Query.prototype.setOptions = function(options, overwrite) {
16701665
return this;
16711666
};
16721667

1673-
/*!
1674-
* ignore
1675-
*/
1676-
1677-
const printOverwriteDeprecationWarning = util.deprecate(
1678-
function printOverwriteDeprecationWarning() {},
1679-
'The `overwrite` option for `findOneAndUpdate()` is deprecated. use `findOneAndReplace()` instead.'
1680-
);
1681-
16821668
/**
16831669
* Sets the [`explain` option](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/),
16841670
* which makes this query return detailed execution stats instead of the actual
@@ -1898,11 +1884,6 @@ Query.prototype._updateForExec = function() {
18981884
while (i--) {
18991885
const op = ops[i];
19001886

1901-
if (this._mongooseOptions.overwrite) {
1902-
ret[op] = update[op];
1903-
continue;
1904-
}
1905-
19061887
if ('$' !== op[0]) {
19071888
// fix up $set sugar
19081889
if (!ret.$set) {
@@ -3259,16 +3240,6 @@ Query.prototype.findOneAndUpdate = function(filter, doc, options) {
32593240
*/
32603241

32613242
Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3262-
// For backwards compability with Mongoose 6 re: #13550
3263-
3264-
if (this._mongooseOptions.overwrite != null) {
3265-
printOverwriteDeprecationWarning();
3266-
}
3267-
if (this._mongooseOptions.overwrite) {
3268-
this.op = 'findOneAndReplace';
3269-
return this._findOneAndReplace();
3270-
}
3271-
32723243
this._castConditions();
32733244

32743245
_castArrayFilters(this);
@@ -3287,7 +3258,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
32873258
convertNewToReturnDocument(options);
32883259
this._applyTranslateAliases(options);
32893260

3290-
this._update = this._castUpdate(this._update, false);
3261+
this._update = this._castUpdate(this._update);
32913262

32923263
const _opts = Object.assign({}, options, {
32933264
setDefaultsOnInsert: this._mongooseOptions.setDefaultsOnInsert
@@ -3520,7 +3491,6 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options) {
35203491
options.returnOriginal = returnOriginal;
35213492
}
35223493
this.setOptions(options);
3523-
this.setOptions({ overwrite: true });
35243494

35253495
return this;
35263496
};
@@ -3755,16 +3725,11 @@ async function _updateThunk(op) {
37553725
this._applyTranslateAliases(options);
37563726

37573727
this._update = clone(this._update, options);
3758-
const isOverwriting = this._mongooseOptions.overwrite && !hasDollarKeys(this._update);
3728+
const isOverwriting = op === 'replaceOne';
37593729
if (isOverwriting) {
3760-
if (op === 'updateOne' || op === 'updateMany') {
3761-
throw new MongooseError('The MongoDB server disallows ' +
3762-
'overwriting documents using `' + op + '`. See: ' +
3763-
'https://mongoosejs.com/docs/deprecations.html#update');
3764-
}
37653730
this._update = new this.model(this._update, null, true);
37663731
} else {
3767-
this._update = this._castUpdate(this._update, this._mongooseOptions.overwrite);
3732+
this._update = this._castUpdate(this._update);
37683733

37693734
if (this._update == null || Object.keys(this._update).length === 0) {
37703735
return { acknowledged: false };
@@ -4065,7 +4030,6 @@ Query.prototype.replaceOne = function(conditions, doc, options, callback) {
40654030
callback = undefined;
40664031
}
40674032

4068-
this.setOptions({ overwrite: true });
40694033
return _update(this, 'replaceOne', conditions, doc, options, callback);
40704034
};
40714035

@@ -4563,15 +4527,14 @@ Query.prototype.post = function(fn) {
45634527
* Casts obj for an update command.
45644528
*
45654529
* @param {Object} obj
4566-
* @param {Boolean} overwrite
45674530
* @return {Object} obj after casting its values
45684531
* @method _castUpdate
45694532
* @memberOf Query
45704533
* @instance
45714534
* @api private
45724535
*/
45734536

4574-
Query.prototype._castUpdate = function _castUpdate(obj, overwrite) {
4537+
Query.prototype._castUpdate = function _castUpdate(obj) {
45754538
let schema = this.schema;
45764539

45774540
const discriminatorKey = schema.options.discriminatorKey;
@@ -4605,7 +4568,6 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) {
46054568
}
46064569

46074570
return castUpdate(schema, obj, {
4608-
overwrite: overwrite,
46094571
strict: this._mongooseOptions.strict,
46104572
upsert: upsert,
46114573
arrayFilters: this.options.arrayFilters,

test/document.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,7 +1985,7 @@ describe('document', function() {
19851985

19861986
const err = await person.save().then(() => null, err => err);
19871987
assert.equal(err instanceof DocumentNotFoundError, true);
1988-
assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`);
1988+
assert.equal(err.message, `No document found for query "{ _id: new ObjectId('${person._id}') }" on model "Person"`);
19891989
});
19901990

19911991
it('saving a document when version bump required, throws a VersionError when document is not found (gh-10974)', async function() {
@@ -2020,7 +2020,7 @@ describe('document', function() {
20202020
}
20212021
catch (err) {
20222022
assert.equal(err instanceof DocumentNotFoundError, true);
2023-
assert.equal(err.message, `No document found for query "{ _id: new ObjectId("${person._id}") }" on model "Person"`);
2023+
assert.equal(err.message, `No document found for query "{ _id: new ObjectId('${person._id}') }" on model "Person"`);
20242024
threw = true;
20252025
}
20262026

0 commit comments

Comments
 (0)