Skip to content

Commit 4b84e5b

Browse files
authored
Merge pull request #5086 from Automattic/3556
When overwriting doc with update+overwrite, validate the whole doc
2 parents 480ab43 + 85726f7 commit 4b84e5b

File tree

4 files changed

+90
-21
lines changed

4 files changed

+90
-21
lines changed

lib/query.js

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var QueryCursor = require('./querycursor');
77
var QueryStream = require('./querystream');
88
var cast = require('./cast');
99
var castUpdate = require('./services/query/castUpdate');
10+
var hasDollarKeys = require('./services/query/hasDollarKeys');
1011
var helpers = require('./queryhelpers');
1112
var mquery = require('mquery');
1213
var readPref = require('./drivers').ReadPreference;
@@ -2001,29 +2002,36 @@ Query.prototype._findAndModify = function(type, callback) {
20012002
opts.remove = false;
20022003
}
20032004

2004-
castedDoc = castDoc(this, opts.overwrite);
2005-
castedDoc = setDefaultsOnInsert(this, schema, castedDoc, opts);
2006-
if (!castedDoc) {
2007-
if (opts.upsert) {
2008-
// still need to do the upsert to empty doc
2009-
var doc = utils.clone(castedQuery);
2010-
delete doc._id;
2011-
castedDoc = {$set: doc};
2012-
} else {
2013-
return this.findOne(callback);
2014-
}
2015-
} else if (castedDoc instanceof Error) {
2016-
return callback(castedDoc);
2005+
if (opts.overwrite && !hasDollarKeys(this._update)) {
2006+
castedDoc = new model(this._update, null, true);
2007+
doValidate = function(callback) {
2008+
castedDoc.validate(callback);
2009+
};
20172010
} else {
2018-
// In order to make MongoDB 2.6 happy (see
2019-
// https://jira.mongodb.org/browse/SERVER-12266 and related issues)
2020-
// if we have an actual update document but $set is empty, junk the $set.
2021-
if (castedDoc.$set && Object.keys(castedDoc.$set).length === 0) {
2022-
delete castedDoc.$set;
2011+
castedDoc = castDoc(this, opts.overwrite);
2012+
castedDoc = setDefaultsOnInsert(this, schema, castedDoc, opts);
2013+
if (!castedDoc) {
2014+
if (opts.upsert) {
2015+
// still need to do the upsert to empty doc
2016+
var doc = utils.clone(castedQuery);
2017+
delete doc._id;
2018+
castedDoc = {$set: doc};
2019+
} else {
2020+
return this.findOne(callback);
2021+
}
2022+
} else if (castedDoc instanceof Error) {
2023+
return callback(castedDoc);
2024+
} else {
2025+
// In order to make MongoDB 2.6 happy (see
2026+
// https://jira.mongodb.org/browse/SERVER-12266 and related issues)
2027+
// if we have an actual update document but $set is empty, junk the $set.
2028+
if (castedDoc.$set && Object.keys(castedDoc.$set).length === 0) {
2029+
delete castedDoc.$set;
2030+
}
20232031
}
2024-
}
20252032

2026-
doValidate = updateValidators(this, schema, castedDoc, opts);
2033+
doValidate = updateValidators(this, schema, castedDoc, opts);
2034+
}
20272035
}
20282036

20292037
this._applyPaths();
@@ -2181,7 +2189,14 @@ Query.prototype._execUpdate = function(callback) {
21812189

21822190
if (this.options.runValidators) {
21832191
_this = this;
2184-
doValidate = updateValidators(this, schema, castedDoc, options);
2192+
if (this.options.overwrite && !hasDollarKeys(castedDoc)) {
2193+
castedDoc = new _this.model(castedDoc, null, true);
2194+
doValidate = function(callback) {
2195+
castedDoc.validate(callback);
2196+
};
2197+
} else {
2198+
doValidate = updateValidators(this, schema, castedDoc, options);
2199+
}
21852200
var _callback = function(err) {
21862201
if (err) {
21872202
return callback(err);

lib/services/query/hasDollarKeys.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
module.exports = function(obj) {
4+
var keys = Object.keys(obj);
5+
var len = keys.length;
6+
for (var i = 0; i < len; ++i) {
7+
if (keys[i].charAt(0) === '$') {
8+
return true;
9+
}
10+
}
11+
return false;
12+
};

test/model.findOneAndUpdate.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,27 @@ describe('model: findOneAndUpdate:', function() {
18281828
});
18291829
});
18301830

1831+
it('overwrite doc with update validators (gh-3556)', function(done) {
1832+
var testSchema = new Schema({
1833+
name: {
1834+
type: String,
1835+
required: true
1836+
},
1837+
otherName: String
1838+
});
1839+
var Test = db.model('gh3556', testSchema);
1840+
1841+
var opts = { overwrite: true, runValidators: true };
1842+
Test.findOneAndUpdate({}, { otherName: 'test' }, opts, function(error) {
1843+
assert.ok(error);
1844+
assert.ok(error.errors['name']);
1845+
Test.findOneAndUpdate({}, { $set: { otherName: 'test' } }, opts, function(error) {
1846+
assert.ifError(error);
1847+
done();
1848+
});
1849+
});
1850+
});
1851+
18311852
it('properly handles casting nested objects in update (gh-4724)', function(done) {
18321853
var locationSchema = new Schema({
18331854
_id: false,

test/model.update.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2465,6 +2465,27 @@ describe('model: update:', function() {
24652465
});
24662466
});
24672467

2468+
it('overwrite doc with update validators (gh-3556)', function(done) {
2469+
var testSchema = new Schema({
2470+
name: {
2471+
type: String,
2472+
required: true
2473+
},
2474+
otherName: String
2475+
});
2476+
var Test = db.model('gh3556', testSchema);
2477+
2478+
var opts = { overwrite: true, runValidators: true };
2479+
Test.update({}, { otherName: 'test' }, opts, function(error) {
2480+
assert.ok(error);
2481+
assert.ok(error.errors['name']);
2482+
Test.update({}, { $set: { otherName: 'test' } }, opts, function(error) {
2483+
assert.ifError(error);
2484+
done();
2485+
});
2486+
});
2487+
});
2488+
24682489
it('does not fail if passing whole doc (gh-5088)', function(done) {
24692490
var schema = new Schema({
24702491
username: String,

0 commit comments

Comments
 (0)