mongoose 5.0.7 → 5.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/History.md +42 -0
- package/README.md +20 -18
- package/browser.js +6 -0
- package/lib/cast.js +34 -7
- package/lib/connection.js +8 -2
- package/lib/document.js +28 -2
- package/lib/drivers/node-mongodb-native/connection.js +10 -1
- package/lib/index.js +1 -1
- package/lib/model.js +7 -3
- package/lib/query.js +17 -17
- package/lib/queryhelpers.js +36 -5
- package/lib/schema/array.js +17 -6
- package/lib/schema/documentarray.js +12 -3
- package/lib/schema/embedded.js +36 -7
- package/lib/schema/objectid.js +2 -0
- package/lib/schema.js +0 -1
- package/lib/schematype.js +29 -6
- package/lib/services/model/discriminator.js +34 -6
- package/lib/services/query/castUpdate.js +28 -8
- package/lib/services/updateValidators.js +44 -18
- package/lib/types/documentarray.js +25 -8
- package/lib/utils.js +8 -2
- package/migrating_to_5.md +45 -0
- package/package.json +5 -5
- package/lib/document_provider.web.js +0 -17
package/History.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
5.0.11 / 2018-03-19
|
|
2
|
+
===================
|
|
3
|
+
* fix(update): handle $pull with $in in update validators #6240
|
|
4
|
+
* fix(query): don't convert undefined to null when casting so driver `ignoreUndefined` option works #6236
|
|
5
|
+
* docs(middleware): add example of using async/await with middleware #6235
|
|
6
|
+
* fix(populate): apply justOne option before `completeMany()` so it works with lean() #6234
|
|
7
|
+
* fix(query): ensure errors in user callbacks aren't caught in init #6195 #6178
|
|
8
|
+
* docs(connections): document dbName option for Atlas connections #6179
|
|
9
|
+
* fix(discriminator): make child schema nested paths overwrite parent schema paths #6076
|
|
10
|
+
|
|
11
|
+
4.13.12 / 2018-03-13
|
|
12
|
+
====================
|
|
13
|
+
* fix(document): make virtual get() return undefined instead of null if no getters #6223
|
|
14
|
+
* docs: fix url in useMongoClient error message #6219 #6217 [lineus](https://github.com/lineus)
|
|
15
|
+
* fix(discriminator): don't copy `discriminators` property from base schema #6122 #6064
|
|
16
|
+
|
|
17
|
+
5.0.10 / 2018-03-12
|
|
18
|
+
===================
|
|
19
|
+
* docs(schematype): add notes re: running setters on queries #6209
|
|
20
|
+
* docs: fix typo #6208 [kamagatos](https://github.com/kamagatos)
|
|
21
|
+
* fix(query): only call setters once on query filter props for findOneAndUpdate and findOneAndRemove #6203
|
|
22
|
+
* docs: elaborate on connection string changes in migration guide #6193
|
|
23
|
+
* fix(document): skip applyDefaults if subdoc is null #6187
|
|
24
|
+
* docs: fix schematypes docs and link to them #6176
|
|
25
|
+
* docs(faq): add FAQs re: array defaults and casting aggregation pipelines #6184 #6176 #6170 [lineus](https://github.com/lineus)
|
|
26
|
+
* fix(document): ensure primitive defaults are set and built-in default functions run before setters #6155
|
|
27
|
+
* fix(query): handle single embedded embedded discriminators in castForQuery #6027
|
|
28
|
+
|
|
29
|
+
5.0.9 / 2018-03-05
|
|
30
|
+
==================
|
|
31
|
+
* perf: bump mongodb -> 3.0.4 to fix SSL perf issue #6065
|
|
32
|
+
|
|
33
|
+
5.0.8 / 2018-03-03
|
|
34
|
+
==================
|
|
35
|
+
* docs: remove obsolete references to `emitIndexErrors` #6186 [isaackwan](https://github.com/isaackwan)
|
|
36
|
+
* fix(query): don't cast findOne() until exec() so setters don't run twice #6157
|
|
37
|
+
* fix: remove document_provider.web.js file #6186
|
|
38
|
+
* fix(discriminator): support custom discriminator model names #6100 [wentout](https://github.com/wentout)
|
|
39
|
+
* fix: support caching calls to `useDb()` #6036 [rocketspacer](https://github.com/rocketspacer)
|
|
40
|
+
* fix(query): add omitUndefined option so setDefaultsOnInsert can kick in on undefined #6034
|
|
41
|
+
* fix: upgrade mongodb -> 3.0.3 for reconnectTries: 0 blocking process exit fix #6028
|
|
42
|
+
|
|
1
43
|
5.0.7 / 2018-02-23
|
|
2
44
|
==================
|
|
3
45
|
* fix: support eachAsync options with aggregation cursor #6169 #6168 [vichle](https://github.com/vichle)
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Mongoose is a [MongoDB](https://www.mongodb.org/) object modeling tool designed
|
|
|
10
10
|
|
|
11
11
|
[mongoosejs.com](http://mongoosejs.com/)
|
|
12
12
|
|
|
13
|
+
[Mongoose 5.0.0](https://github.com/Automattic/mongoose/blob/master/History.md#500--2018-01-17) was released on January 17, 2018. You can find more details on backwards breaking changes in 5.0.0 on [GitHub](https://github.com/Automattic/mongoose/blob/master/migrating_to_5.md).
|
|
14
|
+
|
|
13
15
|
## Support
|
|
14
16
|
|
|
15
17
|
- [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoose)
|
|
@@ -57,7 +59,7 @@ First, we need to define a connection. If your app uses only one database, you s
|
|
|
57
59
|
Both `connect` and `createConnection` take a `mongodb://` URI, or the parameters `host, database, port, options`.
|
|
58
60
|
|
|
59
61
|
```js
|
|
60
|
-
|
|
62
|
+
const mongoose = require('mongoose');
|
|
61
63
|
|
|
62
64
|
mongoose.connect('mongodb://localhost/my_database');
|
|
63
65
|
```
|
|
@@ -73,14 +75,14 @@ Once connected, the `open` event is fired on the `Connection` instance. If you'r
|
|
|
73
75
|
Models are defined through the `Schema` interface.
|
|
74
76
|
|
|
75
77
|
```js
|
|
76
|
-
|
|
78
|
+
const Schema = mongoose.Schema,
|
|
77
79
|
ObjectId = Schema.ObjectId;
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const BlogPost = new Schema({
|
|
82
|
+
author: ObjectId,
|
|
83
|
+
title: String,
|
|
84
|
+
body: String,
|
|
85
|
+
date: Date
|
|
84
86
|
});
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -100,7 +102,7 @@ Aside from defining the structure of your documents and the types of data you're
|
|
|
100
102
|
The following example shows some of these features:
|
|
101
103
|
|
|
102
104
|
```js
|
|
103
|
-
|
|
105
|
+
const Comment = new Schema({
|
|
104
106
|
name: { type: String, default: 'hahaha' },
|
|
105
107
|
age: { type: Number, min: 18, index: true },
|
|
106
108
|
bio: { type: String, match: /[a-z]/ },
|
|
@@ -127,19 +129,19 @@ Take a look at the example in `examples/schema.js` for an end-to-end example of
|
|
|
127
129
|
Once we define a model through `mongoose.model('ModelName', mySchema)`, we can access it through the same function
|
|
128
130
|
|
|
129
131
|
```js
|
|
130
|
-
|
|
132
|
+
const myModel = mongoose.model('ModelName');
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
Or just do it all at once
|
|
134
136
|
|
|
135
137
|
```js
|
|
136
|
-
|
|
138
|
+
const MyModel = mongoose.model('ModelName', mySchema);
|
|
137
139
|
```
|
|
138
140
|
|
|
139
141
|
The first argument is the _singular_ name of the collection your model is for. **Mongoose automatically looks for the _plural_ version of your model name.** For example, if you use
|
|
140
142
|
|
|
141
143
|
```js
|
|
142
|
-
|
|
144
|
+
const MyModel = mongoose.model('Ticket', mySchema);
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
Then Mongoose will create the model for your __tickets__ collection, not your __ticket__ collection.
|
|
@@ -147,7 +149,7 @@ Then Mongoose will create the model for your __tickets__ collection, not your __
|
|
|
147
149
|
Once we have our model, we can then instantiate it, and save it:
|
|
148
150
|
|
|
149
151
|
```js
|
|
150
|
-
|
|
152
|
+
const instance = new MyModel();
|
|
151
153
|
instance.my.key = 'hello';
|
|
152
154
|
instance.save(function (err) {
|
|
153
155
|
//
|
|
@@ -167,18 +169,18 @@ You can also `findOne`, `findById`, `update`, etc. For more details check out [t
|
|
|
167
169
|
**Important!** If you opened a separate connection using `mongoose.createConnection()` but attempt to access the model through `mongoose.model('ModelName')` it will not work as expected since it is not hooked up to an active db connection. In this case access your model through the connection you created:
|
|
168
170
|
|
|
169
171
|
```js
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
const conn = mongoose.createConnection('your connection string');
|
|
173
|
+
const MyModel = conn.model('ModelName', schema);
|
|
174
|
+
const m = new MyModel;
|
|
173
175
|
m.save(); // works
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
vs
|
|
177
179
|
|
|
178
180
|
```js
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
const conn = mongoose.createConnection('your connection string');
|
|
182
|
+
const MyModel = mongoose.model('ModelName', schema);
|
|
183
|
+
const m = new MyModel;
|
|
182
184
|
m.save(); // does not work b/c the default connection object was never connected
|
|
183
185
|
```
|
|
184
186
|
|
package/browser.js
ADDED
package/lib/cast.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/*!
|
|
2
4
|
* Module dependencies.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const StrictModeError = require('./error/strict');
|
|
8
|
+
const Types = require('./schema/index');
|
|
9
|
+
const get = require('lodash.get');
|
|
10
|
+
const util = require('util');
|
|
11
|
+
const utils = require('./utils');
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Handles internal casting for query filters.
|
|
@@ -67,6 +70,31 @@ module.exports = function cast(schema, obj, options, context) {
|
|
|
67
70
|
|
|
68
71
|
schematype = schema.path(path);
|
|
69
72
|
|
|
73
|
+
// Check for embedded discriminator paths
|
|
74
|
+
if (!schematype) {
|
|
75
|
+
let split = path.split('.');
|
|
76
|
+
let j = split.length;
|
|
77
|
+
while (j--) {
|
|
78
|
+
let pathFirstHalf = split.slice(0, j).join('.');
|
|
79
|
+
let pathLastHalf = split.slice(j).join('.');
|
|
80
|
+
let _schematype = schema.path(pathFirstHalf);
|
|
81
|
+
let discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
|
|
82
|
+
// gh-6027: if we haven't found the schematype but this path is
|
|
83
|
+
// underneath an embedded discriminator and the embedded discriminator
|
|
84
|
+
// key is in the query, use the embedded discriminator schema
|
|
85
|
+
if (_schematype != null &&
|
|
86
|
+
get(_schematype, 'schema.discriminators') != null &&
|
|
87
|
+
discriminatorKey != null &&
|
|
88
|
+
pathLastHalf !== discriminatorKey) {
|
|
89
|
+
const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
|
|
90
|
+
if (discriminatorVal) {
|
|
91
|
+
schematype = _schematype.schema.discriminators[discriminatorVal].
|
|
92
|
+
path(pathLastHalf);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
if (!schematype) {
|
|
71
99
|
// Handle potential embedded array queries
|
|
72
100
|
var split = path.split('.'),
|
|
@@ -206,8 +234,7 @@ module.exports = function cast(schema, obj, options, context) {
|
|
|
206
234
|
} else if (options.strictQuery) {
|
|
207
235
|
delete obj[path];
|
|
208
236
|
}
|
|
209
|
-
} else if (val
|
|
210
|
-
obj[path] = null;
|
|
237
|
+
} else if (val == null) {
|
|
211
238
|
continue;
|
|
212
239
|
} else if (val.constructor.name === 'Object') {
|
|
213
240
|
any$conditionals = Object.keys(val).some(function(k) {
|
package/lib/connection.js
CHANGED
|
@@ -59,7 +59,8 @@ function Connection(base) {
|
|
|
59
59
|
this.pass = null;
|
|
60
60
|
this.name = null;
|
|
61
61
|
this.options = null;
|
|
62
|
-
this.otherDbs = [];
|
|
62
|
+
this.otherDbs = []; // FIXME: To be replaced with relatedDbs
|
|
63
|
+
this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
|
|
63
64
|
this.states = STATES;
|
|
64
65
|
this._readyState = STATES.disconnected;
|
|
65
66
|
this._closeCalled = false;
|
|
@@ -103,11 +104,16 @@ Object.defineProperty(Connection.prototype, 'readyState', {
|
|
|
103
104
|
|
|
104
105
|
if (this._readyState !== val) {
|
|
105
106
|
this._readyState = val;
|
|
106
|
-
// loop over the otherDbs on this connection and change their state
|
|
107
|
+
// [legacy] loop over the otherDbs on this connection and change their state
|
|
107
108
|
for (var i = 0; i < this.otherDbs.length; i++) {
|
|
108
109
|
this.otherDbs[i].readyState = val;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// loop over relatedDbs on this connection and change their state
|
|
113
|
+
for (var k in this.relatedDbs) {
|
|
114
|
+
this.relatedDbs[k].readyState = val;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
if (STATES.connected === val) {
|
|
112
118
|
this._hasOpened = true;
|
|
113
119
|
}
|
package/lib/document.js
CHANGED
|
@@ -87,6 +87,10 @@ function Document(obj, fields, skipId, options) {
|
|
|
87
87
|
|
|
88
88
|
this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren);
|
|
89
89
|
|
|
90
|
+
// By default, defaults get applied **before** setting initial values
|
|
91
|
+
// Re: gh-6155
|
|
92
|
+
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true);
|
|
93
|
+
|
|
90
94
|
if (obj) {
|
|
91
95
|
if (obj instanceof Document) {
|
|
92
96
|
this.isNew = obj.isNew;
|
|
@@ -99,7 +103,10 @@ function Document(obj, fields, skipId, options) {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
// Function defaults get applied **after** setting initial values so they
|
|
107
|
+
// see the full doc rather than an empty one, unless they opt out.
|
|
108
|
+
// Re: gh-3781, gh-6155
|
|
109
|
+
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false);
|
|
103
110
|
|
|
104
111
|
this.$__._id = this._id;
|
|
105
112
|
|
|
@@ -202,7 +209,7 @@ function $__hasIncludedChildren(fields) {
|
|
|
202
209
|
* ignore
|
|
203
210
|
*/
|
|
204
211
|
|
|
205
|
-
function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
212
|
+
function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters) {
|
|
206
213
|
const paths = Object.keys(doc.schema.paths);
|
|
207
214
|
const plen = paths.length;
|
|
208
215
|
|
|
@@ -222,6 +229,10 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
|
222
229
|
let doc_ = doc._doc;
|
|
223
230
|
|
|
224
231
|
for (let j = 0; j < len; ++j) {
|
|
232
|
+
if (doc_ == null) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
let piece = path[j];
|
|
226
237
|
curPath += (!curPath.length ? '' : '.') + piece;
|
|
227
238
|
|
|
@@ -242,6 +253,18 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
|
242
253
|
break;
|
|
243
254
|
}
|
|
244
255
|
|
|
256
|
+
if (typeof type.defaultValue === 'function') {
|
|
257
|
+
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
} else if (!isBeforeSetters) {
|
|
264
|
+
// Non-function defaults should always run **before** setters
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
245
268
|
if (fields && exclude !== null) {
|
|
246
269
|
if (exclude === true) {
|
|
247
270
|
// apply defaults to all non-excluded fields
|
|
@@ -1009,6 +1032,9 @@ Document.prototype.get = function(path, type) {
|
|
|
1009
1032
|
var obj = this._doc;
|
|
1010
1033
|
|
|
1011
1034
|
if (schema instanceof VirtualType) {
|
|
1035
|
+
if (schema.getters.length === 0) {
|
|
1036
|
+
return void 0;
|
|
1037
|
+
}
|
|
1012
1038
|
return schema.applyGetters(null, this);
|
|
1013
1039
|
}
|
|
1014
1040
|
|
|
@@ -40,7 +40,10 @@ NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
|
|
|
40
40
|
* @api public
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
NativeConnection.prototype.useDb = function(name) {
|
|
43
|
+
NativeConnection.prototype.useDb = function(name, options) {
|
|
44
|
+
// Return immediately if cached
|
|
45
|
+
if (options && options.useCache && this.relatedDbs[name]) return this.relatedDbs[name];
|
|
46
|
+
|
|
44
47
|
// we have to manually copy all of the attributes...
|
|
45
48
|
var newConn = new this.constructor();
|
|
46
49
|
newConn.name = name;
|
|
@@ -88,6 +91,12 @@ NativeConnection.prototype.useDb = function(name) {
|
|
|
88
91
|
this.otherDbs.push(newConn);
|
|
89
92
|
newConn.otherDbs.push(this);
|
|
90
93
|
|
|
94
|
+
// push onto the relatedDbs cache, this is used when state changes
|
|
95
|
+
if (options && options.useCache) {
|
|
96
|
+
this.relatedDbs[newConn.name] = newConn;
|
|
97
|
+
newConn.relatedDbs = this.relatedDbs;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
return newConn;
|
|
92
101
|
};
|
|
93
102
|
|
package/lib/index.js
CHANGED
package/lib/model.js
CHANGED
|
@@ -22,6 +22,7 @@ var applyStatics = require('./services/model/applyStatics');
|
|
|
22
22
|
var cast = require('./cast');
|
|
23
23
|
var castUpdate = require('./services/query/castUpdate');
|
|
24
24
|
var discriminator = require('./services/model/discriminator');
|
|
25
|
+
var getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
|
|
25
26
|
var internalToObjectOptions = require('./options').internalToObjectOptions;
|
|
26
27
|
var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
|
|
27
28
|
var get = require('lodash.get');
|
|
@@ -829,10 +830,11 @@ Model.prototype.model = function model(name) {
|
|
|
829
830
|
*
|
|
830
831
|
* @param {String} name discriminator model name
|
|
831
832
|
* @param {Schema} schema discriminator model schema
|
|
833
|
+
* @param {String} value discriminator model typeKey value
|
|
832
834
|
* @api public
|
|
833
835
|
*/
|
|
834
836
|
|
|
835
|
-
Model.discriminator = function(name, schema) {
|
|
837
|
+
Model.discriminator = function(name, schema, value) {
|
|
836
838
|
var model;
|
|
837
839
|
if (typeof name === 'function') {
|
|
838
840
|
model = name;
|
|
@@ -842,7 +844,7 @@ Model.discriminator = function(name, schema) {
|
|
|
842
844
|
}
|
|
843
845
|
}
|
|
844
846
|
|
|
845
|
-
schema = discriminator(this, name, schema);
|
|
847
|
+
schema = discriminator(this, name, schema, value);
|
|
846
848
|
if (this.db.models[name]) {
|
|
847
849
|
throw new OverwriteModelError(name);
|
|
848
850
|
}
|
|
@@ -2034,7 +2036,7 @@ Model.create = function create(doc, callback) {
|
|
|
2034
2036
|
args.forEach(doc => {
|
|
2035
2037
|
toExecute.push(callback => {
|
|
2036
2038
|
var Model = this.discriminators && doc[discriminatorKey] ?
|
|
2037
|
-
this.discriminators[doc[discriminatorKey]] :
|
|
2039
|
+
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this, doc[discriminatorKey]) :
|
|
2038
2040
|
this;
|
|
2039
2041
|
var toSave = doc;
|
|
2040
2042
|
var callbackWrapper = (error, doc) => {
|
|
@@ -3267,6 +3269,8 @@ function assignVals(o) {
|
|
|
3267
3269
|
} else {
|
|
3268
3270
|
rawIds[i] = [rawIds[i]];
|
|
3269
3271
|
}
|
|
3272
|
+
} else if (o.isVirtual && o.justOne && Array.isArray(rawIds[i])) {
|
|
3273
|
+
rawIds[i] = rawIds[i][0];
|
|
3270
3274
|
}
|
|
3271
3275
|
|
|
3272
3276
|
if (o.isVirtual && docs[i].constructor.name === 'model') {
|
package/lib/query.js
CHANGED
|
@@ -1025,6 +1025,10 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1025
1025
|
this._mongooseOptions.useFindAndModify = options.useFindAndModify;
|
|
1026
1026
|
delete options.useFindAndModify;
|
|
1027
1027
|
}
|
|
1028
|
+
if ('omitUndefined' in options) {
|
|
1029
|
+
this._mongooseOptions.omitUndefined = options.omitUndefined;
|
|
1030
|
+
delete options.omitUndefined;
|
|
1031
|
+
}
|
|
1028
1032
|
|
|
1029
1033
|
return Query.base.setOptions.call(this, options);
|
|
1030
1034
|
};
|
|
@@ -1446,10 +1450,10 @@ function completeMany(model, docs, fields, userProvidedFields, pop, callback) {
|
|
|
1446
1450
|
error = error || _error;
|
|
1447
1451
|
}
|
|
1448
1452
|
if (error != null) {
|
|
1449
|
-
--count || callback(error);
|
|
1453
|
+
--count || process.nextTick(() => callback(error));
|
|
1450
1454
|
return;
|
|
1451
1455
|
}
|
|
1452
|
-
--count || callback(error, arr);
|
|
1456
|
+
--count || process.nextTick(() => callback(error, arr));
|
|
1453
1457
|
}
|
|
1454
1458
|
for (var i = 0; i < len; ++i) {
|
|
1455
1459
|
arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields);
|
|
@@ -1595,13 +1599,6 @@ Query.prototype.findOne = function(conditions, projection, options, callback) {
|
|
|
1595
1599
|
this.merge(conditions);
|
|
1596
1600
|
|
|
1597
1601
|
prepareDiscriminatorCriteria(this);
|
|
1598
|
-
|
|
1599
|
-
try {
|
|
1600
|
-
this.cast(this.model);
|
|
1601
|
-
this.error(null);
|
|
1602
|
-
} catch (err) {
|
|
1603
|
-
this.error(err);
|
|
1604
|
-
}
|
|
1605
1602
|
} else if (conditions != null) {
|
|
1606
1603
|
this.error(new ObjectParameterError(conditions, 'filter', 'findOne'));
|
|
1607
1604
|
}
|
|
@@ -2000,14 +1997,14 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop,
|
|
|
2000
1997
|
|
|
2001
1998
|
function _init(err) {
|
|
2002
1999
|
if (err) {
|
|
2003
|
-
return callback(err);
|
|
2000
|
+
return process.nextTick(() => callback(err));
|
|
2004
2001
|
}
|
|
2005
2002
|
|
|
2006
2003
|
if (options.rawResult) {
|
|
2007
2004
|
res.value = casted;
|
|
2008
|
-
return callback(null, res);
|
|
2005
|
+
return process.nextTick(() => callback(null, res));
|
|
2009
2006
|
}
|
|
2010
|
-
callback(null, casted);
|
|
2007
|
+
process.nextTick(() => callback(null, casted));
|
|
2011
2008
|
}
|
|
2012
2009
|
}
|
|
2013
2010
|
|
|
@@ -2148,8 +2145,6 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options, callback) {
|
|
|
2148
2145
|
*/
|
|
2149
2146
|
|
|
2150
2147
|
Query.prototype._findOneAndUpdate = function(callback) {
|
|
2151
|
-
this._castConditions();
|
|
2152
|
-
|
|
2153
2148
|
if (this.error() != null) {
|
|
2154
2149
|
return callback(this.error());
|
|
2155
2150
|
}
|
|
@@ -2243,8 +2238,6 @@ Query.prototype.findOneAndRemove = function(conditions, options, callback) {
|
|
|
2243
2238
|
* @api private
|
|
2244
2239
|
*/
|
|
2245
2240
|
Query.prototype._findOneAndRemove = function(callback) {
|
|
2246
|
-
this._castConditions();
|
|
2247
|
-
|
|
2248
2241
|
if (this.error() != null) {
|
|
2249
2242
|
return callback(this.error());
|
|
2250
2243
|
}
|
|
@@ -3071,9 +3064,16 @@ Query.prototype._castUpdate = function _castUpdate(obj, overwrite) {
|
|
|
3071
3064
|
} else {
|
|
3072
3065
|
strict = true;
|
|
3073
3066
|
}
|
|
3067
|
+
|
|
3068
|
+
var omitUndefined = false;
|
|
3069
|
+
if ('omitUndefined' in this._mongooseOptions) {
|
|
3070
|
+
omitUndefined = this._mongooseOptions.omitUndefined;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3074
3073
|
return castUpdate(this.schema, obj, {
|
|
3075
3074
|
overwrite: overwrite,
|
|
3076
|
-
strict: strict
|
|
3075
|
+
strict: strict,
|
|
3076
|
+
omitUndefined
|
|
3077
3077
|
}, this);
|
|
3078
3078
|
};
|
|
3079
3079
|
|
package/lib/queryhelpers.js
CHANGED
|
@@ -46,6 +46,34 @@ exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query,
|
|
|
46
46
|
return pop;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
|
|
50
|
+
/*!
|
|
51
|
+
* returns discriminator by discriminatorMapping.value
|
|
52
|
+
*
|
|
53
|
+
* @param {Model} model
|
|
54
|
+
* @param {string} value
|
|
55
|
+
*/
|
|
56
|
+
function getDiscriminatorByValue(model, value) {
|
|
57
|
+
var discriminator = null;
|
|
58
|
+
if (!model.discriminators) {
|
|
59
|
+
return discriminator;
|
|
60
|
+
}
|
|
61
|
+
for (var name in model.discriminators) {
|
|
62
|
+
var it = model.discriminators[name];
|
|
63
|
+
if (
|
|
64
|
+
it.schema &&
|
|
65
|
+
it.schema.discriminatorMapping &&
|
|
66
|
+
it.schema.discriminatorMapping.value == value
|
|
67
|
+
) {
|
|
68
|
+
discriminator = it;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return discriminator;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exports.getDiscriminatorByValue = getDiscriminatorByValue;
|
|
76
|
+
|
|
49
77
|
/*!
|
|
50
78
|
* If the document is a mapped discriminator type, it returns a model instance for that type, otherwise,
|
|
51
79
|
* it returns an instance of the given model.
|
|
@@ -65,11 +93,14 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
|
|
|
65
93
|
? discriminatorMapping.key
|
|
66
94
|
: null;
|
|
67
95
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
var
|
|
71
|
-
|
|
72
|
-
|
|
96
|
+
var value = doc[key];
|
|
97
|
+
if (key && value && model.discriminators) {
|
|
98
|
+
var discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value);
|
|
99
|
+
if (discriminator) {
|
|
100
|
+
var _fields = utils.clone(userProvidedFields);
|
|
101
|
+
exports.applyPaths(_fields, discriminator.schema);
|
|
102
|
+
return new discriminator(undefined, _fields, true);
|
|
103
|
+
}
|
|
73
104
|
}
|
|
74
105
|
|
|
75
106
|
return new model(undefined, fields, true);
|
package/lib/schema/array.js
CHANGED
|
@@ -22,6 +22,7 @@ var util = require('util');
|
|
|
22
22
|
var utils = require('../utils');
|
|
23
23
|
var castToNumber = require('./operators/helpers').castToNumber;
|
|
24
24
|
var geospatial = require('./operators/geospatial');
|
|
25
|
+
var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
|
|
25
26
|
|
|
26
27
|
var MongooseArray;
|
|
27
28
|
var EmbeddedDoc;
|
|
@@ -93,7 +94,7 @@ function SchemaArray(key, cast, options, schemaOptions) {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
if (!('defaultValue' in this) || this.defaultValue !== void 0) {
|
|
96
|
-
|
|
97
|
+
var defaultFn = function() {
|
|
97
98
|
var arr = [];
|
|
98
99
|
if (fn) {
|
|
99
100
|
arr = defaultArr();
|
|
@@ -102,7 +103,9 @@ function SchemaArray(key, cast, options, schemaOptions) {
|
|
|
102
103
|
}
|
|
103
104
|
// Leave it up to `cast()` to convert the array
|
|
104
105
|
return arr;
|
|
105
|
-
}
|
|
106
|
+
};
|
|
107
|
+
defaultFn.$runBeforeSetters = true;
|
|
108
|
+
this.default(defaultFn);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
|
|
@@ -247,10 +250,18 @@ SchemaArray.prototype.castForQuery = function($conditional, value) {
|
|
|
247
250
|
|
|
248
251
|
if (val &&
|
|
249
252
|
Constructor.discriminators &&
|
|
250
|
-
Constructor.schema
|
|
251
|
-
|
|
252
|
-
Constructor.
|
|
253
|
-
|
|
253
|
+
Constructor.schema &&
|
|
254
|
+
Constructor.schema.options &&
|
|
255
|
+
Constructor.schema.options.discriminatorKey) {
|
|
256
|
+
if (typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
|
|
257
|
+
Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
|
|
258
|
+
Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
|
|
259
|
+
} else {
|
|
260
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]);
|
|
261
|
+
if (constructorByValue) {
|
|
262
|
+
Constructor = constructorByValue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
254
265
|
}
|
|
255
266
|
|
|
256
267
|
var proto = this.casterConstructor.prototype;
|
|
@@ -12,6 +12,7 @@ var SchemaType = require('../schematype');
|
|
|
12
12
|
var discriminator = require('../services/model/discriminator');
|
|
13
13
|
var util = require('util');
|
|
14
14
|
var utils = require('../utils');
|
|
15
|
+
var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
|
|
15
16
|
|
|
16
17
|
var MongooseDocumentArray;
|
|
17
18
|
var Subdocument;
|
|
@@ -291,9 +292,17 @@ DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
|
|
|
291
292
|
|
|
292
293
|
var Constructor = this.casterConstructor;
|
|
293
294
|
if (Constructor.discriminators &&
|
|
294
|
-
|
|
295
|
-
Constructor.
|
|
296
|
-
|
|
295
|
+
Constructor.schema &&
|
|
296
|
+
Constructor.schema.options &&
|
|
297
|
+
typeof value[i][Constructor.schema.options.discriminatorKey] === 'string') {
|
|
298
|
+
if (Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]]) {
|
|
299
|
+
Constructor = Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]];
|
|
300
|
+
} else {
|
|
301
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, value[i][Constructor.schema.options.discriminatorKey]);
|
|
302
|
+
if (constructorByValue) {
|
|
303
|
+
Constructor = constructorByValue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
297
306
|
}
|
|
298
307
|
|
|
299
308
|
// Check if the document has a different schema (re gh-3701)
|
package/lib/schema/embedded.js
CHANGED
|
@@ -11,6 +11,7 @@ const castToNumber = require('./operators/helpers').castToNumber;
|
|
|
11
11
|
const discriminator = require('../services/model/discriminator');
|
|
12
12
|
const geospatial = require('./operators/geospatial');
|
|
13
13
|
const internalToObjectOptions = require('../options').internalToObjectOptions;
|
|
14
|
+
var getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
|
|
14
15
|
|
|
15
16
|
let Subdocument;
|
|
16
17
|
|
|
@@ -140,9 +141,15 @@ Embedded.prototype.cast = function(val, doc, init, priorVal) {
|
|
|
140
141
|
var discriminatorKey = Constructor.schema.options.discriminatorKey;
|
|
141
142
|
if (val != null &&
|
|
142
143
|
Constructor.discriminators &&
|
|
143
|
-
typeof val[discriminatorKey] === 'string'
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
typeof val[discriminatorKey] === 'string') {
|
|
145
|
+
if (Constructor.discriminators[val[discriminatorKey]]) {
|
|
146
|
+
Constructor = Constructor.discriminators[val[discriminatorKey]];
|
|
147
|
+
} else {
|
|
148
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, val[discriminatorKey]);
|
|
149
|
+
if (constructorByValue) {
|
|
150
|
+
Constructor = constructorByValue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
var subdoc;
|
|
@@ -188,7 +195,22 @@ Embedded.prototype.castForQuery = function($conditional, val) {
|
|
|
188
195
|
val = this._applySetters(val);
|
|
189
196
|
}
|
|
190
197
|
|
|
191
|
-
|
|
198
|
+
var Constructor = this.caster;
|
|
199
|
+
var discriminatorKey = Constructor.schema.options.discriminatorKey;
|
|
200
|
+
if (val != null &&
|
|
201
|
+
Constructor.discriminators &&
|
|
202
|
+
typeof val[discriminatorKey] === 'string') {
|
|
203
|
+
if (Constructor.discriminators[val[discriminatorKey]]) {
|
|
204
|
+
Constructor = Constructor.discriminators[val[discriminatorKey]];
|
|
205
|
+
} else {
|
|
206
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, val[discriminatorKey]);
|
|
207
|
+
if (constructorByValue) {
|
|
208
|
+
Constructor = constructorByValue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return new Constructor(val);
|
|
192
214
|
};
|
|
193
215
|
|
|
194
216
|
/**
|
|
@@ -202,11 +224,18 @@ Embedded.prototype.doValidate = function(value, fn, scope) {
|
|
|
202
224
|
var discriminatorKey = Constructor.schema.options.discriminatorKey;
|
|
203
225
|
if (value != null &&
|
|
204
226
|
Constructor.discriminators &&
|
|
205
|
-
typeof value[discriminatorKey] === 'string'
|
|
206
|
-
|
|
207
|
-
|
|
227
|
+
typeof value[discriminatorKey] === 'string') {
|
|
228
|
+
if (Constructor.discriminators[value[discriminatorKey]]) {
|
|
229
|
+
Constructor = Constructor.discriminators[value[discriminatorKey]];
|
|
230
|
+
} else {
|
|
231
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, value[discriminatorKey]);
|
|
232
|
+
if (constructorByValue) {
|
|
233
|
+
Constructor = constructorByValue;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
208
236
|
}
|
|
209
237
|
|
|
238
|
+
|
|
210
239
|
SchemaType.prototype.doValidate.call(this, value, function(error) {
|
|
211
240
|
if (error) {
|
|
212
241
|
return fn(error);
|
package/lib/schema/objectid.js
CHANGED
package/lib/schema.js
CHANGED
|
@@ -29,7 +29,6 @@ var mpath = require('mpath');
|
|
|
29
29
|
* - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
|
|
30
30
|
* - [capped](/docs/guide.html#capped): bool - defaults to false
|
|
31
31
|
* - [collection](/docs/guide.html#collection): string - no default
|
|
32
|
-
* - [emitIndexErrors](/docs/guide.html#emitIndexErrors): bool - defaults to false.
|
|
33
32
|
* - [id](/docs/guide.html#id): bool - defaults to true
|
|
34
33
|
* - [_id](/docs/guide.html#_id): bool - defaults to true
|
|
35
34
|
* - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
|
package/lib/schematype.js
CHANGED
|
@@ -245,15 +245,19 @@ SchemaType.prototype.sparse = function(bool) {
|
|
|
245
245
|
* }
|
|
246
246
|
*
|
|
247
247
|
* // defining within the schema
|
|
248
|
-
* var s = new Schema({ name: { type: String, set: capitalize }})
|
|
248
|
+
* var s = new Schema({ name: { type: String, set: capitalize }});
|
|
249
249
|
*
|
|
250
|
-
* // or
|
|
250
|
+
* // or with the SchemaType
|
|
251
251
|
* var s = new Schema({ name: String })
|
|
252
|
-
* s.path('name').set(capitalize)
|
|
252
|
+
* s.path('name').set(capitalize);
|
|
253
253
|
*
|
|
254
|
-
* Setters allow you to transform the data before it gets to the raw mongodb
|
|
254
|
+
* Setters allow you to transform the data before it gets to the raw mongodb
|
|
255
|
+
* document or query.
|
|
255
256
|
*
|
|
256
|
-
* Suppose you are implementing user registration for a website. Users provide
|
|
257
|
+
* Suppose you are implementing user registration for a website. Users provide
|
|
258
|
+
* an email and password, which gets saved to mongodb. The email is a string
|
|
259
|
+
* that you will want to normalize to lower case, in order to avoid one email
|
|
260
|
+
* having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
|
|
257
261
|
*
|
|
258
262
|
* You can set up email lower case normalization easily via a Mongoose setter.
|
|
259
263
|
*
|
|
@@ -276,7 +280,8 @@ SchemaType.prototype.sparse = function(bool) {
|
|
|
276
280
|
* console.log(user.email); // 'avenue@q.com'
|
|
277
281
|
* User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com'
|
|
278
282
|
*
|
|
279
|
-
* As you can see above, setters allow you to transform the data before it
|
|
283
|
+
* As you can see above, setters allow you to transform the data before it
|
|
284
|
+
* stored in MongoDB, or before executing a query.
|
|
280
285
|
*
|
|
281
286
|
* _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._
|
|
282
287
|
*
|
|
@@ -303,6 +308,24 @@ SchemaType.prototype.sparse = function(bool) {
|
|
|
303
308
|
* console.log(v.name); // name is required
|
|
304
309
|
* console.log(v.taxonomy); // Parvovirinae
|
|
305
310
|
*
|
|
311
|
+
* You can also use setters to modify other properties on the document. If
|
|
312
|
+
* you're setting a property `name` on a document, the setter will run with
|
|
313
|
+
* `this` as the document. Be careful, in mongoose 5 setters will also run
|
|
314
|
+
* when querying by `name` with `this` as the query.
|
|
315
|
+
*
|
|
316
|
+
* ```javascript
|
|
317
|
+
* const nameSchema = new Schema({ name: String, keywords: [String] });
|
|
318
|
+
* nameSchema.path('name').set(function(v) {
|
|
319
|
+
* // Need to check if `this` is a document, because in mongoose 5
|
|
320
|
+
* // setters will also run on queries, in which case `this` will be a
|
|
321
|
+
* // mongoose query object.
|
|
322
|
+
* if (this instanceof Document && v != null) {
|
|
323
|
+
* this.keywords = v.split(' ');
|
|
324
|
+
* }
|
|
325
|
+
* return v;
|
|
326
|
+
* });
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
306
329
|
* @param {Function} fn
|
|
307
330
|
* @return {SchemaType} this
|
|
308
331
|
* @api public
|
|
@@ -14,7 +14,7 @@ var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
|
|
|
14
14
|
* ignore
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
module.exports = function discriminator(model, name, schema) {
|
|
17
|
+
module.exports = function discriminator(model, name, schema, tiedValue) {
|
|
18
18
|
if (!(schema && schema.instanceOfSchema)) {
|
|
19
19
|
throw new Error('You must pass a valid discriminator Schema');
|
|
20
20
|
}
|
|
@@ -46,6 +46,11 @@ module.exports = function discriminator(model, name, schema) {
|
|
|
46
46
|
'" cannot have field with name "' + key + '"');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
var value = name;
|
|
50
|
+
if (typeof tiedValue == 'string' && tiedValue.length) {
|
|
51
|
+
value = tiedValue;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
function merge(schema, baseSchema) {
|
|
50
55
|
if (baseSchema.paths._id &&
|
|
51
56
|
baseSchema.paths._id.options &&
|
|
@@ -55,15 +60,38 @@ module.exports = function discriminator(model, name, schema) {
|
|
|
55
60
|
delete schema.paths._id;
|
|
56
61
|
delete schema.tree._id;
|
|
57
62
|
}
|
|
58
|
-
|
|
63
|
+
|
|
64
|
+
// Find conflicting paths: if something is a path in the base schema
|
|
65
|
+
// and a nested path in the child schema, overwrite the base schema path.
|
|
66
|
+
// See gh-6076
|
|
67
|
+
var baseSchemaPaths = Object.keys(baseSchema.paths);
|
|
68
|
+
var conflictingPaths = [];
|
|
69
|
+
for (i = 0; i < baseSchemaPaths.length; ++i) {
|
|
70
|
+
if (schema.nested[baseSchemaPaths[i]]) {
|
|
71
|
+
conflictingPaths.push(baseSchemaPaths[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
utils.merge(schema, baseSchema, {
|
|
76
|
+
omit: { discriminators: true },
|
|
77
|
+
omitNested: conflictingPaths.reduce((cur, path) => {
|
|
78
|
+
cur['tree.' + path] = true;
|
|
79
|
+
return cur;
|
|
80
|
+
}, {})
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Clean up conflicting paths _after_ merging re: gh-6076
|
|
84
|
+
for (i = 0; i < conflictingPaths.length; ++i) {
|
|
85
|
+
delete schema.paths[conflictingPaths[i]];
|
|
86
|
+
}
|
|
59
87
|
|
|
60
88
|
var obj = {};
|
|
61
89
|
obj[key] = {
|
|
62
|
-
default:
|
|
90
|
+
default: value,
|
|
63
91
|
select: true,
|
|
64
92
|
set: function(newName) {
|
|
65
|
-
if (newName ===
|
|
66
|
-
return
|
|
93
|
+
if (newName === value) {
|
|
94
|
+
return value;
|
|
67
95
|
}
|
|
68
96
|
throw new Error('Can\'t set discriminator key "' + key + '"');
|
|
69
97
|
},
|
|
@@ -71,7 +99,7 @@ module.exports = function discriminator(model, name, schema) {
|
|
|
71
99
|
};
|
|
72
100
|
obj[key][schema.options.typeKey] = String;
|
|
73
101
|
schema.add(obj);
|
|
74
|
-
schema.discriminatorMapping = {key: key, value:
|
|
102
|
+
schema.discriminatorMapping = {key: key, value: value, isRoot: false};
|
|
75
103
|
|
|
76
104
|
if (baseSchema.options.collection) {
|
|
77
105
|
schema.options.collection = baseSchema.options.collection;
|
|
@@ -71,7 +71,7 @@ module.exports = function castUpdate(schema, obj, options, context) {
|
|
|
71
71
|
typeof val === 'object' &&
|
|
72
72
|
!Buffer.isBuffer(val) &&
|
|
73
73
|
(!overwrite || hasDollarKey)) {
|
|
74
|
-
hasKeys |= walkUpdatePath(schema, val, op, options
|
|
74
|
+
hasKeys |= walkUpdatePath(schema, val, op, options, context);
|
|
75
75
|
} else if (overwrite && ret && typeof ret === 'object') {
|
|
76
76
|
// if we are just using overwrite, cast the query and then we will
|
|
77
77
|
// *always* return the value, even if it is an empty object. We need to
|
|
@@ -79,7 +79,7 @@ module.exports = function castUpdate(schema, obj, options, context) {
|
|
|
79
79
|
// user passes {} and wants to clobber the whole document
|
|
80
80
|
// Also, _walkUpdatePath expects an operation, so give it $set since that
|
|
81
81
|
// is basically what we're doing
|
|
82
|
-
walkUpdatePath(schema, ret, '$set', options
|
|
82
|
+
walkUpdatePath(schema, ret, '$set', options, context);
|
|
83
83
|
} else {
|
|
84
84
|
var msg = 'Invalid atomic update value for ' + op + '. '
|
|
85
85
|
+ 'Expected an object, received ' + typeof val;
|
|
@@ -97,14 +97,17 @@ module.exports = function castUpdate(schema, obj, options, context) {
|
|
|
97
97
|
* @param {Schema} schema
|
|
98
98
|
* @param {Object} obj - part of a query
|
|
99
99
|
* @param {String} op - the atomic operator ($pull, $set, etc)
|
|
100
|
-
* @param {
|
|
100
|
+
* @param {Object} options
|
|
101
|
+
* @param {Boolean|String} [options.strict]
|
|
102
|
+
* @param {Boolean} [options.omitUndefined]
|
|
101
103
|
* @param {Query} context
|
|
102
104
|
* @param {String} pref - path prefix (internal only)
|
|
103
105
|
* @return {Bool} true if this path has keys to update
|
|
104
106
|
* @api private
|
|
105
107
|
*/
|
|
106
108
|
|
|
107
|
-
function walkUpdatePath(schema, obj, op,
|
|
109
|
+
function walkUpdatePath(schema, obj, op, options, context, pref) {
|
|
110
|
+
var strict = options.strict;
|
|
108
111
|
var prefix = pref ? pref + '.' : '';
|
|
109
112
|
var keys = Object.keys(obj);
|
|
110
113
|
var i = keys.length;
|
|
@@ -126,9 +129,8 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
126
129
|
schematype = schema._getSchema(prefix + key);
|
|
127
130
|
if (schematype && schematype.caster && op in castOps) {
|
|
128
131
|
// embedded doc schema
|
|
129
|
-
hasKeys = true;
|
|
130
|
-
|
|
131
132
|
if ('$each' in val) {
|
|
133
|
+
hasKeys = true;
|
|
132
134
|
try {
|
|
133
135
|
obj[key] = {
|
|
134
136
|
$each: castUpdateVal(schematype, val.$each, op, context)
|
|
@@ -154,6 +156,13 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
154
156
|
} catch (error) {
|
|
155
157
|
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
|
156
158
|
}
|
|
159
|
+
|
|
160
|
+
if (options.omitUndefined && obj[key] === void 0) {
|
|
161
|
+
delete obj[key];
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
hasKeys = true;
|
|
157
166
|
}
|
|
158
167
|
} else if ((op === '$currentDate') || (op in castOps && schematype)) {
|
|
159
168
|
// $currentDate can take an object
|
|
@@ -163,6 +172,11 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
163
172
|
aggregatedError = _handleCastError(error, context, key, aggregatedError);
|
|
164
173
|
}
|
|
165
174
|
|
|
175
|
+
if (options.omitUndefined && obj[key] === void 0) {
|
|
176
|
+
delete obj[key];
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
hasKeys = true;
|
|
167
181
|
} else {
|
|
168
182
|
var pathToCheck = (prefix + key);
|
|
@@ -187,7 +201,7 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
187
201
|
// gh-2314
|
|
188
202
|
// we should be able to set a schema-less field
|
|
189
203
|
// to an empty object literal
|
|
190
|
-
hasKeys |= walkUpdatePath(schema, val, op,
|
|
204
|
+
hasKeys |= walkUpdatePath(schema, val, op, options, context, prefix + key) ||
|
|
191
205
|
(utils.isObject(val) && Object.keys(val).length === 0);
|
|
192
206
|
}
|
|
193
207
|
} else {
|
|
@@ -222,7 +236,6 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
222
236
|
continue;
|
|
223
237
|
}
|
|
224
238
|
|
|
225
|
-
hasKeys = true;
|
|
226
239
|
try {
|
|
227
240
|
obj[key] = castUpdateVal(schematype, val, op, key, context);
|
|
228
241
|
} catch (error) {
|
|
@@ -232,6 +245,13 @@ function walkUpdatePath(schema, obj, op, strict, context, pref) {
|
|
|
232
245
|
if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
|
|
233
246
|
obj[key] = { $each: obj[key] };
|
|
234
247
|
}
|
|
248
|
+
|
|
249
|
+
if (options.omitUndefined && obj[key] === void 0) {
|
|
250
|
+
delete obj[key];
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
hasKeys = true;
|
|
235
255
|
}
|
|
236
256
|
}
|
|
237
257
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/*!
|
|
2
4
|
* Module dependencies.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const Mixed = require('../schema/mixed');
|
|
8
|
+
const ValidationError = require('../error/validation');
|
|
9
|
+
const flatten = require('./common').flatten;
|
|
10
|
+
const modifiedPaths = require('./common').modifiedPaths;
|
|
11
|
+
const parallel = require('async/parallel');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Applies validators and defaults to update and findOneAndUpdate operations,
|
|
@@ -57,6 +59,11 @@ module.exports = function(query, schema, castedDoc, options) {
|
|
|
57
59
|
var updatedPath = paths[j].replace('.$.', '.0.');
|
|
58
60
|
updatedPath = updatedPath.replace(/\.\$$/, '.0');
|
|
59
61
|
key = keys[i];
|
|
62
|
+
// With `$pull` we might flatten `$in`. Skip stuff nested under `$in`
|
|
63
|
+
// for the rest of the logic, it will get handled later.
|
|
64
|
+
if (updatedPath.indexOf('$') !== -1) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
60
67
|
if (key === '$set' || key === '$setOnInsert' ||
|
|
61
68
|
key === '$pull' || key === '$pullAll') {
|
|
62
69
|
updatedValues[updatedPath] = flat[paths[j]];
|
|
@@ -86,19 +93,38 @@ module.exports = function(query, schema, castedDoc, options) {
|
|
|
86
93
|
if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
|
|
87
94
|
return;
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
function(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
|
|
97
|
+
if (v && Array.isArray(v.$in)) {
|
|
98
|
+
v.$in.forEach((v, i) => {
|
|
99
|
+
validatorsToExecute.push(function(callback) {
|
|
100
|
+
schemaPath.doValidate(
|
|
101
|
+
v,
|
|
102
|
+
function(err) {
|
|
103
|
+
if (err) {
|
|
104
|
+
err.path = updates[i] + '.$in.' + i;
|
|
105
|
+
validationErrors.push(err);
|
|
106
|
+
}
|
|
107
|
+
callback(null);
|
|
108
|
+
},
|
|
109
|
+
options && options.context === 'query' ? query : null,
|
|
110
|
+
{updateValidator: true});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
validatorsToExecute.push(function(callback) {
|
|
115
|
+
schemaPath.doValidate(
|
|
116
|
+
v,
|
|
117
|
+
function(err) {
|
|
118
|
+
if (err) {
|
|
119
|
+
err.path = updates[i];
|
|
120
|
+
validationErrors.push(err);
|
|
121
|
+
}
|
|
122
|
+
callback(null);
|
|
123
|
+
},
|
|
124
|
+
options && options.context === 'query' ? query : null,
|
|
125
|
+
{updateValidator: true});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
102
128
|
}
|
|
103
129
|
}
|
|
104
130
|
for (i = 0; i < numUpdates; ++i) {
|
|
@@ -10,6 +10,7 @@ const ObjectIdSchema = require('../schema/objectid');
|
|
|
10
10
|
const internalToObjectOptions = require('../options').internalToObjectOptions;
|
|
11
11
|
const utils = require('../utils');
|
|
12
12
|
const Document = require('../document');
|
|
13
|
+
const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* DocumentArray constructor
|
|
@@ -123,10 +124,18 @@ MongooseDocumentArray.mixin = {
|
|
|
123
124
|
|
|
124
125
|
if (value &&
|
|
125
126
|
Constructor.discriminators &&
|
|
126
|
-
Constructor.schema
|
|
127
|
-
|
|
128
|
-
Constructor.
|
|
129
|
-
|
|
127
|
+
Constructor.schema &&
|
|
128
|
+
Constructor.schema.options &&
|
|
129
|
+
Constructor.schema.options.discriminatorKey) {
|
|
130
|
+
if (typeof value[Constructor.schema.options.discriminatorKey] === 'string' &&
|
|
131
|
+
Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) {
|
|
132
|
+
Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]];
|
|
133
|
+
} else {
|
|
134
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, value[Constructor.schema.options.discriminatorKey]);
|
|
135
|
+
if (constructorByValue) {
|
|
136
|
+
Constructor = constructorByValue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
return new Constructor(value, this, undefined, undefined, index);
|
|
@@ -233,10 +242,18 @@ MongooseDocumentArray.mixin = {
|
|
|
233
242
|
var Constructor = this._schema.casterConstructor;
|
|
234
243
|
if (obj &&
|
|
235
244
|
Constructor.discriminators &&
|
|
236
|
-
Constructor.schema
|
|
237
|
-
|
|
238
|
-
Constructor.
|
|
239
|
-
|
|
245
|
+
Constructor.schema &&
|
|
246
|
+
Constructor.schema.options &&
|
|
247
|
+
Constructor.schema.options.discriminatorKey) {
|
|
248
|
+
if (typeof obj[Constructor.schema.options.discriminatorKey] === 'string' &&
|
|
249
|
+
Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) {
|
|
250
|
+
Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]];
|
|
251
|
+
} else {
|
|
252
|
+
var constructorByValue = getDiscriminatorByValue(Constructor, obj[Constructor.schema.options.discriminatorKey]);
|
|
253
|
+
if (constructorByValue) {
|
|
254
|
+
Constructor = constructorByValue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
240
257
|
}
|
|
241
258
|
|
|
242
259
|
return new Constructor(obj);
|
package/lib/utils.js
CHANGED
|
@@ -306,7 +306,7 @@ exports.random = function() {
|
|
|
306
306
|
* @api private
|
|
307
307
|
*/
|
|
308
308
|
|
|
309
|
-
exports.merge = function merge(to, from, options) {
|
|
309
|
+
exports.merge = function merge(to, from, options, path) {
|
|
310
310
|
options = options || {};
|
|
311
311
|
|
|
312
312
|
const keys = Object.keys(from);
|
|
@@ -314,18 +314,24 @@ exports.merge = function merge(to, from, options) {
|
|
|
314
314
|
const len = keys.length;
|
|
315
315
|
let key;
|
|
316
316
|
|
|
317
|
+
path = path || '';
|
|
318
|
+
const omitNested = options.omitNested || {};
|
|
319
|
+
|
|
317
320
|
while (i < len) {
|
|
318
321
|
key = keys[i++];
|
|
319
322
|
if (options.omit && options.omit[key]) {
|
|
320
323
|
continue;
|
|
321
324
|
}
|
|
325
|
+
if (omitNested[path]) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
322
328
|
if (to[key] == null) {
|
|
323
329
|
to[key] = from[key];
|
|
324
330
|
} else if (exports.isObject(from[key])) {
|
|
325
331
|
if (!exports.isObject(to[key])) {
|
|
326
332
|
to[key] = {};
|
|
327
333
|
}
|
|
328
|
-
merge(to[key], from[key], options);
|
|
334
|
+
merge(to[key], from[key], options, path ? path + '.' + key : key);
|
|
329
335
|
} else if (options.overwrite) {
|
|
330
336
|
to[key] = from[key];
|
|
331
337
|
}
|
package/migrating_to_5.md
CHANGED
|
@@ -33,6 +33,21 @@ mongoose.connect('mongodb://localhost:27017/test');
|
|
|
33
33
|
mongoose.model('Test', new Schema({}));
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Connection Logic and `useMongoClient`
|
|
37
|
+
|
|
38
|
+
The [`useMongoClient` option](http://mongoosejs.com/docs/4.x/docs/connections.html#use-mongo-client) was
|
|
39
|
+
removed in Mongoose 5, it is now always `true`. As a consequence, Mongoose 5
|
|
40
|
+
no longer supports several function signatures for `mongoose.connect()` that
|
|
41
|
+
worked in Mongoose 4.x if the `useMongoClient` option was off. Below are some
|
|
42
|
+
examples of `mongoose.connect()` calls that do **not** work in Mongoose 5.x.
|
|
43
|
+
|
|
44
|
+
* `mongoose.connect('localhost', 27017);`
|
|
45
|
+
* `mongoose.connect('localhost', 'mydb', 27017);`
|
|
46
|
+
* `mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');`
|
|
47
|
+
|
|
48
|
+
In Mongoose 5.x, the first parameter to `mongoose.connect()` and `mongoose.createConnection()`, if specified, **must** be a [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/). The
|
|
49
|
+
connection string and options are then passed down to [the MongoDB Node.js driver's `MongoClient.connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#.connect). Mongoose does not modify the connection string, although `mongoose.connect()` and `mongoose.createConnection()` support a [few additional options in addition to the ones the MongoDB driver supports](http://mongoosejs.com/docs/connections.html#options).
|
|
50
|
+
|
|
36
51
|
### Setter Order
|
|
37
52
|
|
|
38
53
|
Setters run in reverse order in 4.x:
|
|
@@ -134,6 +149,36 @@ The above code does **not** work in 5.x, you **must** wrap the `$match` and `$sk
|
|
|
134
149
|
MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);
|
|
135
150
|
```
|
|
136
151
|
|
|
152
|
+
### Boolean Casting
|
|
153
|
+
|
|
154
|
+
By default, mongoose 4 would coerce any value to a boolean without error.
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
// Fine in mongoose 4, would save a doc with `boolField = true`
|
|
158
|
+
const MyModel = mongoose.model('Test', new Schema({
|
|
159
|
+
boolField: Boolean
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
MyModel.create({ boolField: 'not a boolean' });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Mongoose 5 only casts the following values to `true`:
|
|
166
|
+
|
|
167
|
+
* `true`
|
|
168
|
+
* `'true'`
|
|
169
|
+
* `1`
|
|
170
|
+
* `'1'`
|
|
171
|
+
* `'yes'`
|
|
172
|
+
|
|
173
|
+
And the following values to `false`:
|
|
174
|
+
|
|
175
|
+
* `false`
|
|
176
|
+
* `'false'`
|
|
177
|
+
* `0`
|
|
178
|
+
* `'0'`
|
|
179
|
+
* `'no'`
|
|
180
|
+
|
|
181
|
+
All other values will cause a `CastError`
|
|
137
182
|
### Query Casting
|
|
138
183
|
|
|
139
184
|
Casting for `update()`, `updateOne()`, `updateMany()`, `replaceOne()`,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "5.0.
|
|
4
|
+
"version": "5.0.11",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"bson": "~1.0.4",
|
|
24
24
|
"kareem": "2.0.5",
|
|
25
25
|
"lodash.get": "4.4.2",
|
|
26
|
-
"mongodb": "3.0.
|
|
26
|
+
"mongodb": "3.0.4",
|
|
27
27
|
"mongoose-legacy-pluralize": "1.0.2",
|
|
28
28
|
"mpath": "0.3.0",
|
|
29
29
|
"mquery": "3.0.0",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"fix-lint": "eslint . --fix",
|
|
65
65
|
"lint": "eslint . --quiet",
|
|
66
66
|
"nsp": "nsp check",
|
|
67
|
-
"release": "git push origin master --tags && npm publish",
|
|
68
|
-
"release-legacy": "git push origin 4.x --tags && npm publish --tag legacy",
|
|
67
|
+
"release": "git pull && git push origin master --tags && npm publish",
|
|
68
|
+
"release-legacy": "git pull origin 4.x && git push origin 4.x --tags && npm publish --tag legacy",
|
|
69
69
|
"test": "mocha --exit test/*.test.js test/**/*.test.js",
|
|
70
70
|
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
|
71
71
|
},
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"url": "git://github.com/Automattic/mongoose.git"
|
|
82
82
|
},
|
|
83
83
|
"homepage": "http://mongoosejs.com",
|
|
84
|
-
"browser": "
|
|
84
|
+
"browser": "./browser.js",
|
|
85
85
|
"eslintConfig": {
|
|
86
86
|
"extends": [
|
|
87
87
|
"eslint:recommended"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* eslint-env browser */
|
|
4
|
-
|
|
5
|
-
/*!
|
|
6
|
-
* Module dependencies.
|
|
7
|
-
*/
|
|
8
|
-
var BrowserDocument = require('./browserDocument.js');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Returns the Document constructor for the current context
|
|
12
|
-
*
|
|
13
|
-
* @api private
|
|
14
|
-
*/
|
|
15
|
-
module.exports = function() {
|
|
16
|
-
return BrowserDocument;
|
|
17
|
-
};
|