diff --git a/docs/guide.pug b/docs/guide.pug index 442496d09cc..e71cfefe32d 100644 --- a/docs/guide.pug +++ b/docs/guide.pug @@ -688,7 +688,7 @@ block content _Note that Mongoose does not send the `shardcollection` command for you. You must configure your shards yourself._ -

option: strict

+

option: strict

The strict option, (enabled by default), ensures that values passed to our model constructor that were not specified in our schema do not get saved to @@ -739,7 +739,7 @@ block content thing.save(); // iAmNotInTheSchema is never saved to the db ``` -

option: strictQuery

+

option: strictQuery

For backwards compatibility, the `strict` option does **not** apply to the `filter` parameter for queries. diff --git a/lib/index.js b/lib/index.js index b0d3e086151..4d113c18a0d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -160,6 +160,7 @@ Mongoose.prototype.driver = require('./driver'); * - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject) * - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()` * - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. + * - 'strictQuery': false by default, may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas. * - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. * - 'typePojoToMixed': true by default, may be `false` or `true`. Sets the default typePojoToMixed for schemas. * - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query diff --git a/lib/model.js b/lib/model.js index d7a6feda7ab..73d38045073 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3381,6 +3381,19 @@ Model.$__insertMany = function(arr, options, callback) { _this.collection.insertMany(docObjects, options, function(error, res) { if (error) { + // `writeErrors` is a property reported by the MongoDB driver, + // just not if there's only 1 error. + if (error.writeErrors == null && + get(error, 'result.result.writeErrors') != null) { + error.writeErrors = error.result.result.writeErrors; + } + + // `insertedDocs` is a Mongoose-specific property + const erroredIndexes = new Set(error.writeErrors.map(err => err.index)); + error.insertedDocs = docAttributes.filter((doc, i) => { + return !erroredIndexes.has(i); + }); + callback(error, null); return; } diff --git a/lib/schema.js b/lib/schema.js index 062b5be8321..1724dd08be9 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -400,6 +400,7 @@ Schema.prototype.defaultOptions = function(options) { const baseOptions = get(this, 'base.options', {}); options = utils.options({ strict: 'strict' in baseOptions ? baseOptions.strict : true, + strictQuery: 'strictQuery' in baseOptions ? baseOptions.strictQuery : false, bufferCommands: true, capped: false, // { size, max, autoIndexId } versionKey: '__v', diff --git a/lib/types/core_array.js b/lib/types/core_array.js index b6632359daa..c4c6b33b441 100644 --- a/lib/types/core_array.js +++ b/lib/types/core_array.js @@ -879,7 +879,7 @@ class CoreMongooseArray extends Array { * * ####Note: * - * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwriting any changes that happen between when you retrieved the object and when you save it._ * * @api public * @method unshift @@ -889,8 +889,14 @@ class CoreMongooseArray extends Array { unshift() { _checkManualPopulation(this, arguments); - let values = [].map.call(arguments, this._cast, this); - values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]); + let values; + if (this[arraySchemaSymbol] == null) { + values = arguments; + } else { + values = [].map.call(arguments, this._cast, this); + values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]); + } + [].unshift.apply(this, values); this._registerAtomic('$set', this); this._markModified(); diff --git a/lib/validoptions.js b/lib/validoptions.js index 9464e4d8611..68a7b424dfd 100644 --- a/lib/validoptions.js +++ b/lib/validoptions.js @@ -17,6 +17,7 @@ const VALID_OPTIONS = Object.freeze([ 'runValidators', 'selectPopulatedPaths', 'strict', + 'strictQuery', 'toJSON', 'toObject', 'useCreateIndex', diff --git a/test/model.test.js b/test/model.test.js index 52ad90d9252..3c59e5afb73 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -4528,6 +4528,42 @@ describe('Model', function() { } }); + it('insertMany() `writeErrors` if only one error (gh-8938)', function() { + const QuestionType = new mongoose.Schema({ + code: { type: String, required: true, unique: true }, + text: String + }); + const Question = db.model('Test', QuestionType); + + return co(function*() { + yield Question.init(); + + yield Question.create({ code: 'MEDIUM', text: '123' }); + const data = [ + { code: 'MEDIUM', text: '1111' }, + { code: 'test', text: '222' }, + { code: 'HARD', text: '2222' } + ]; + const opts = { ordered: false, rawResult: true }; + let err = yield Question.insertMany(data, opts).catch(err => err); + assert.ok(Array.isArray(err.writeErrors)); + assert.equal(err.writeErrors.length, 1); + assert.equal(err.insertedDocs.length, 2); + assert.equal(err.insertedDocs[0].code, 'test'); + assert.equal(err.insertedDocs[1].code, 'HARD'); + + yield Question.deleteMany({}); + yield Question.create({ code: 'MEDIUM', text: '123' }); + yield Question.create({ code: 'HARD', text: '123' }); + + err = yield Question.insertMany(data, opts).catch(err => err); + assert.ok(Array.isArray(err.writeErrors)); + assert.equal(err.writeErrors.length, 2); + assert.equal(err.insertedDocs.length, 1); + assert.equal(err.insertedDocs[0].code, 'test'); + }); + }); + it('insertMany() ordered option for single validation error', function(done) { start.mongodVersion(function(err, version) { if (err) { diff --git a/test/schema.test.js b/test/schema.test.js index b01d5f7d248..bbb82886605 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2425,6 +2425,35 @@ describe('schema', function() { }); }); + describe('mongoose.set(`strictQuery`, value); (gh-6658)', function() { + let strictQueryOriginalValue; + + this.beforeEach(() => strictQueryOriginalValue = mongoose.get('strictQuery')); + this.afterEach(() => mongoose.set('strictQuery', strictQueryOriginalValue)); + + it('setting `strictQuery` on base sets strictQuery to schema (gh-6658)', function() { + // Arrange + mongoose.set('strictQuery', 'some value'); + + // Act + const schema = new Schema(); + + // Assert + assert.equal(schema.get('strictQuery'), 'some value'); + }); + + it('`strictQuery` set on base gets overwritten by option set on schema (gh-6658)', function() { + // Arrange + mongoose.set('strictQuery', 'base option'); + + // Act + const schema = new Schema({}, { strictQuery: 'schema option' }); + + // Assert + assert.equal(schema.get('strictQuery'), 'schema option'); + }); + }); + it('treats dotted paths with no parent as a nested path (gh-9020)', function() { const customerSchema = new Schema({ 'card.brand': String, diff --git a/test/types.documentarray.test.js b/test/types.documentarray.test.js index 22d9db368a9..93bafeccaa8 100644 --- a/test/types.documentarray.test.js +++ b/test/types.documentarray.test.js @@ -611,6 +611,27 @@ describe('types.documentarray', function() { 'd' ]); }); + + it('unshift() after map() works (gh-9012)', function() { + const MyModel = db.model('Test', Schema({ + myArray: [{ name: String }] + })); + + const doc = new MyModel({ + myArray: [{ name: 'b' }, { name: 'c' }] + }); + let myArray = doc.myArray; + + myArray = myArray.map(val => ({ name: `${val.name} mapped` })); + + myArray.unshift({ name: 'a inserted' }); + + assert.deepEqual(myArray.map(v => v.name), [ + 'a inserted', + 'b mapped', + 'c mapped' + ]); + }); }); it('cleans modified subpaths on splice() (gh-7249)', function() {