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
+
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
+
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() {