Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 48 additions & 47 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@
for (const key in delta[1]['$set']) {
if (options.pathsToSave.includes(key)) {
continue;
} else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
} else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.') ||
options.pathsToSave.some(pathToSave => pathToSave.slice(0, key.length) === key && pathToSave.charAt(key.length) === '.')) {
continue;
} else {
delete delta[1]['$set'][key];
Expand Down Expand Up @@ -631,7 +632,7 @@
* @instance
*/

Model.prototype.$__version = function(where, delta) {
Model.prototype.$__version = function (where, delta) {

Check failure on line 635 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
const key = this.$__schema.options.versionKey;
if (where === true) {
// this is an insert
Expand Down Expand Up @@ -740,7 +741,7 @@

Model.prototype.deleteOne = function deleteOne(options) {
if (typeof options === 'function' ||
typeof arguments[1] === 'function') {
typeof arguments[1] === 'function') {
throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
}

Expand Down Expand Up @@ -911,7 +912,7 @@
* @api public
*/

Model.discriminator = function(name, schema, options) {
Model.discriminator = function (name, schema, options) {

Check failure on line 915 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
let model;
if (typeof name === 'function') {
model = name;
Expand Down Expand Up @@ -1042,7 +1043,7 @@
}

const conn = this.db;
const _ensureIndexes = async() => {
const _ensureIndexes = async () => {

Check failure on line 1046 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
const autoIndex = utils.getOption(
'autoIndex',
this.schema.options,
Expand All @@ -1054,7 +1055,7 @@
}
return await this.ensureIndexes({ _automatic: true });
};
const _createSearchIndexes = async() => {
const _createSearchIndexes = async () => {

Check failure on line 1058 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
const autoSearchIndex = utils.getOption(
'autoSearchIndex',
this.schema.options,
Expand All @@ -1067,7 +1068,7 @@

return await this.createSearchIndexes();
};
const _createCollection = async() => {
const _createCollection = async () => {

Check failure on line 1071 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
let autoCreate = utils.getOption(
'autoCreate',
this.schema.options,
Expand Down Expand Up @@ -1099,7 +1100,7 @@

const _catch = this.$init.catch;
const _this = this;
this.$init.catch = function() {
this.$init.catch = function () {

Check failure on line 1103 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
_this.$caught = true;
return _catch.apply(_this.$init, arguments);
};
Expand Down Expand Up @@ -1620,7 +1621,7 @@
let indexError;

options = options || {};
const done = function(err) {
const done = function (err) {

Check failure on line 1624 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
if (err && !model.$caught) {
model.emit('error', err);
}
Expand All @@ -1638,24 +1639,24 @@
}

if (!indexes.length) {
immediate(function() {
immediate(function () {

Check failure on line 1642 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
done();
});
return;
}
// Indexes are created one-by-one

const indexSingleDone = function(err, fields, options, name) {
const indexSingleDone = function (err, fields, options, name) {

Check failure on line 1649 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
model.emit('index-single-done', err, fields, options, name);
};
const indexSingleStart = function(fields, options) {
const indexSingleStart = function (fields, options) {

Check failure on line 1652 in lib/model.js

View workflow job for this annotation

GitHub Actions / Lint JS-Files

Unexpected space before function parentheses
model.emit('index-single-start', fields, options);
};

const baseSchema = model.schema._baseSchema;
const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : [];

immediate(function() {
immediate(function () {
// If buffering is off, do this manually.
if (options._automatic && !model.collection.collection) {
model.collection.addQueue(create, []);
Expand All @@ -1668,7 +1669,7 @@
function create() {
if (options._automatic) {
if (model.schema.options.autoIndex === false ||
(model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
(model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
return done();
}
}
Expand Down Expand Up @@ -2326,7 +2327,7 @@
* @api public
*/

Model.findOneAndUpdate = function(conditions, update, options) {
Model.findOneAndUpdate = function (conditions, update, options) {
_checkContext(this, 'findOneAndUpdate');
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
throw new MongooseError('Model.findOneAndUpdate() no longer accepts a callback');
Expand Down Expand Up @@ -2413,7 +2414,7 @@
* @api public
*/

Model.findByIdAndUpdate = function(id, update, options) {
Model.findByIdAndUpdate = function (id, update, options) {
_checkContext(this, 'findByIdAndUpdate');
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
throw new MongooseError('Model.findByIdAndUpdate() no longer accepts a callback');
Expand Down Expand Up @@ -2466,7 +2467,7 @@
* @api public
*/

Model.findOneAndDelete = function(conditions, options) {
Model.findOneAndDelete = function (conditions, options) {
_checkContext(this, 'findOneAndDelete');

if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
Expand Down Expand Up @@ -2503,7 +2504,7 @@
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
*/

Model.findByIdAndDelete = function(id, options) {
Model.findByIdAndDelete = function (id, options) {
_checkContext(this, 'findByIdAndDelete');

if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
Expand Down Expand Up @@ -2546,7 +2547,7 @@
* @api public
*/

Model.findOneAndReplace = function(filter, replacement, options) {
Model.findOneAndReplace = function (filter, replacement, options) {
_checkContext(this, 'findOneAndReplace');

if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
Expand Down Expand Up @@ -2597,7 +2598,7 @@

Model.create = async function create(doc, options) {
if (typeof options === 'function' ||
typeof arguments[2] === 'function') {
typeof arguments[2] === 'function') {
throw new MongooseError('Model.create() no longer accepts a callback');
}

Expand Down Expand Up @@ -2629,12 +2630,12 @@
}

if (args.length === 2 &&
args[0] != null &&
args[1] != null &&
args[0].session == null &&
last &&
getConstructorName(last.session) === 'ClientSession' &&
!this.schema.path('session')) {
args[0] != null &&
args[1] != null &&
args[0].session == null &&
last &&
getConstructorName(last.session) === 'ClientSession' &&
!this.schema.path('session')) {
// Probably means the user is running into the common mistake of trying
// to use a spread to specify options, see gh-7535
utils.warn('WARNING: to pass a `session` to `Model.create()` in ' +
Expand Down Expand Up @@ -2664,7 +2665,7 @@
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
`found for model "${this.modelName}"`);
}
let toSave = doc;
if (!(toSave instanceof Model)) {
Expand All @@ -2689,7 +2690,7 @@
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
`found for model "${this.modelName}"`);
}
let toSave = doc;

Expand All @@ -2710,7 +2711,7 @@
this;
if (Model == null) {
throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
`found for model "${this.modelName}"`);
`found for model "${this.modelName}"`);
}
try {
let toSave = doc;
Expand Down Expand Up @@ -2817,7 +2818,7 @@
* @api public
*/

Model.watch = function(pipeline, options) {
Model.watch = function (pipeline, options) {
_checkContext(this, 'watch');

options = options || {};
Expand Down Expand Up @@ -2873,7 +2874,7 @@
* @api public
*/

Model.startSession = function() {
Model.startSession = function () {
_checkContext(this, 'startSession');

return this.db.startSession.apply(this.db, arguments);
Expand Down Expand Up @@ -2995,7 +2996,7 @@
}

// We filter all failed pre-validations by removing nulls
const docAttributes = docs.filter(function(doc) {
const docAttributes = docs.filter(function (doc) {
return doc != null;
});
for (let i = 0; i < docAttributes.length; ++i) {
Expand Down Expand Up @@ -3033,7 +3034,7 @@
}
return [];
}
const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
const docObjects = lean ? docAttributes : docAttributes.map(function (doc) {
if (doc.$__schema.options.versionKey) {
doc[doc.$__schema.options.versionKey] = 0;
}
Expand All @@ -3054,7 +3055,7 @@
// `writeErrors` is a property reported by the MongoDB driver,
// just not if there's only 1 error.
if (error.writeErrors == null &&
(error.result && error.result.result && error.result.result.writeErrors) != null) {
(error.result && error.result.result && error.result.result.writeErrors) != null) {
error.writeErrors = error.result.result.writeErrors;
}

Expand Down Expand Up @@ -3281,7 +3282,7 @@
_checkContext(this, 'bulkWrite');

if (typeof options === 'function' ||
typeof arguments[2] === 'function') {
typeof arguments[2] === 'function') {
throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
}
options = options || {};
Expand Down Expand Up @@ -3830,7 +3831,7 @@
* @api public
*/

Model.hydrate = function(obj, projection, options) {
Model.hydrate = function (obj, projection, options) {
_checkContext(this, 'hydrate');

if (options?.virtuals && options?.hydratedPopulatedDocs === false) {
Expand Down Expand Up @@ -4004,9 +4005,9 @@
options = typeof options === 'function' ? options : clone(options);

const versionKey = model &&
model.schema &&
model.schema.options &&
model.schema.options.versionKey || null;
model.schema &&
model.schema.options &&
model.schema.options.versionKey || null;
decorateUpdateWithVersionKey(doc, options, versionKey);

return mq[op](conditions, doc, options);
Expand Down Expand Up @@ -4341,9 +4342,9 @@
// to fail. So delay running lean transform until _after_
// `_assign()`
if (mod.options &&
mod.options.options &&
mod.options.options.lean &&
mod.options.options.lean.transform) {
mod.options.options &&
mod.options.options.lean &&
mod.options.options.lean.transform) {
mod.options.options._leanTransform = mod.options.options.lean.transform;
mod.options.options.lean = true;
}
Expand Down Expand Up @@ -4475,8 +4476,8 @@
// field, that's the client's fault.
for (const foreignField of mod.foreignField) {
if (foreignField !== '_id' &&
query.selectedInclusively() &&
!isPathSelectedInclusive(query._fields, foreignField)) {
query.selectedInclusively() &&
!isPathSelectedInclusive(query._fields, foreignField)) {
query.select(foreignField);
}
}
Expand Down Expand Up @@ -4741,7 +4742,7 @@
model.$__collection = collection;

// Create custom query constructor
model.Query = function() {
model.Query = function () {
Query.apply(this, arguments);
};
Object.setPrototypeOf(model.Query.prototype, Query.prototype);
Expand Down Expand Up @@ -4891,7 +4892,7 @@
Model.collection = Model.prototype.collection;
Model.$__collection = Model.collection;
// Errors handled internally, so ignore
Model.init().catch(() => {});
Model.init().catch(() => { });
return Model;
};

Expand Down Expand Up @@ -4947,7 +4948,7 @@
* @api public
*/

Model.inspect = function() {
Model.inspect = function () {
return `Model { ${this.modelName} }`;
};

Expand Down
48 changes: 48 additions & 0 deletions test/gh-paths-to-save.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const start = require('./common');
const assert = require('assert');
const mongoose = start.mongoose;
const Schema = mongoose.Schema;

describe('gh-paths-to-save', function () {
let db;
let Model;

before(function () {
db = start();
});

after(async function () {
await db.close();
});

beforeEach(() => db.deleteModel(/.*/));
afterEach(() => db.deleteModel(/.*/));
afterEach(() => db.dropDatabase());

it('should allow saving parent paths of whitelisted paths (gh-issue)', async function () {
const schema = new Schema({
nested: {
a: Number,
b: Number
}
});

Model = db.model('Test', schema);

const doc = new Model({ nested: { a: 1, b: 1 } });
await doc.save();

// Modify the parent path
doc.nested = { a: 2, b: 2 };

// Try to save ONLY nested.a
// This should allow the update to nested.a to go through, even if it means saving the whole nested object
await doc.save({ pathsToSave: ['nested.a'] });

const found = await Model.findById(doc._id);
assert.strictEqual(found.nested.a, 2);
assert.strictEqual(found.nested.b, 2);
});
});