Skip to content

Commit 771a304

Browse files
committed
BREAKING CHANGE: apply minimize by default when updating document
Fix #13782
1 parent f552fe2 commit 771a304

File tree

5 files changed

+71
-48
lines changed

5 files changed

+71
-48
lines changed

lib/document.js

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const isExclusive = require('./helpers/projection/isExclusive');
3333
const inspect = require('util').inspect;
3434
const internalToObjectOptions = require('./options').internalToObjectOptions;
3535
const markArraySubdocsPopulated = require('./helpers/populate/markArraySubdocsPopulated');
36+
const minimize = require('./helpers/minimize');
3637
const mpath = require('mpath');
3738
const queryhelpers = require('./queryhelpers');
3839
const utils = require('./utils');
@@ -3938,42 +3939,6 @@ Document.prototype.toObject = function(options) {
39383939
return this.$toObject(options);
39393940
};
39403941

3941-
/**
3942-
* Minimizes an object, removing undefined values and empty objects
3943-
*
3944-
* @param {Object} object to minimize
3945-
* @return {Object}
3946-
* @api private
3947-
*/
3948-
3949-
function minimize(obj) {
3950-
const keys = Object.keys(obj);
3951-
let i = keys.length;
3952-
let hasKeys;
3953-
let key;
3954-
let val;
3955-
3956-
while (i--) {
3957-
key = keys[i];
3958-
val = obj[key];
3959-
3960-
if (utils.isPOJO(val)) {
3961-
obj[key] = minimize(val);
3962-
}
3963-
3964-
if (undefined === obj[key]) {
3965-
delete obj[key];
3966-
continue;
3967-
}
3968-
3969-
hasKeys = true;
3970-
}
3971-
3972-
return hasKeys
3973-
? obj
3974-
: undefined;
3975-
}
3976-
39773942
/*!
39783943
* Applies virtuals properties to `json`.
39793944
*/

lib/helpers/minimize.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
const { isPOJO } = require('../utils');
4+
5+
module.exports = minimize;
6+
7+
/**
8+
* Minimizes an object, removing undefined values and empty objects
9+
*
10+
* @param {Object} object to minimize
11+
* @return {Object}
12+
* @api private
13+
*/
14+
15+
function minimize(obj) {
16+
const keys = Object.keys(obj);
17+
let i = keys.length;
18+
let hasKeys;
19+
let key;
20+
let val;
21+
22+
while (i--) {
23+
key = keys[i];
24+
val = obj[key];
25+
26+
if (isPOJO(val)) {
27+
obj[key] = minimize(val);
28+
}
29+
30+
if (undefined === obj[key]) {
31+
delete obj[key];
32+
continue;
33+
}
34+
35+
hasKeys = true;
36+
}
37+
38+
return hasKeys
39+
? obj
40+
: undefined;
41+
}

lib/model.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const STATES = require('./connectionstate');
6565
const util = require('util');
6666
const utils = require('./utils');
6767
const MongooseBulkWriteError = require('./error/bulkWriteError');
68+
const minimize = require('./helpers/minimize');
6869

6970
const VERSION_WHERE = 1;
7071
const VERSION_INC = 2;
@@ -341,7 +342,19 @@ Model.prototype.$__handleSave = function(options, callback) {
341342
}
342343

343344
_applyCustomWhere(this, where);
344-
this[modelCollectionSymbol].updateOne(where, delta[1], saveOptions).then(
345+
346+
const update = delta[1];
347+
if (this.$__schema.options.minimize) {
348+
minimize(update);
349+
// minimize might leave us with an empty object, which would
350+
// lead to MongoDB throwing a "Update document requires atomic operators" error
351+
if (Object.keys(update).length === 0) {
352+
handleEmptyUpdate.call(this);
353+
return;
354+
}
355+
}
356+
357+
this[modelCollectionSymbol].updateOne(where, update, saveOptions).then(
345358
ret => {
346359
ret.$where = where;
347360
callback(null, ret);
@@ -353,6 +366,17 @@ Model.prototype.$__handleSave = function(options, callback) {
353366
}
354367
);
355368
} else {
369+
handleEmptyUpdate.call(this);
370+
return;
371+
}
372+
373+
// store the modified paths before the document is reset
374+
this.$__.modifiedPaths = this.modifiedPaths();
375+
this.$__reset();
376+
377+
_setIsNew(this, false);
378+
379+
function handleEmptyUpdate() {
356380
const optionsWithCustomValues = Object.assign({}, options, saveOptions);
357381
const where = this.$__where();
358382
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
@@ -369,14 +393,7 @@ Model.prototype.$__handleSave = function(options, callback) {
369393
callback(null, { $where: where, matchedCount });
370394
})
371395
.catch(callback);
372-
return;
373396
}
374-
375-
// store the modified paths before the document is reset
376-
this.$__.modifiedPaths = this.modifiedPaths();
377-
this.$__reset();
378-
379-
_setIsNew(this, false);
380397
};
381398

382399
/*!

test/document.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3249,7 +3249,7 @@ describe('document', function() {
32493249
field1: { type: Number, default: 1 }
32503250
}
32513251
}
3252-
});
3252+
}, { minimize: false });
32533253

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

@@ -3268,7 +3268,7 @@ describe('document', function() {
32683268
field1: { type: Number, default: 1 }
32693269
}
32703270
}
3271-
});
3271+
}, { minimize: false });
32723272

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

@@ -5008,7 +5008,7 @@ describe('document', function() {
50085008
}).
50095009
then(function(doc) {
50105010
doc.child = {};
5011-
return doc.save();
5011+
return Parent.updateOne({ _id: doc._id }, { $set: { child: {} } }, { minimize: false });
50125012
}).
50135013
then(function() {
50145014
return Parent.findOne();

test/types/schema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,4 +1217,4 @@ function gh13800() {
12171217
expectType<string>(this.lastName);
12181218
expectError<string>(this.someOtherField);
12191219
});
1220-
}
1220+
}

0 commit comments

Comments
 (0)