mongoose 5.0.17 → 5.1.2
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/.travis.yml +4 -3
- package/History.md +61 -0
- package/lib/aggregate.js +15 -4
- package/lib/connection.js +12 -0
- package/lib/document.js +79 -18
- package/lib/drivers/browser/objectid.js +12 -0
- package/lib/drivers/node-mongodb-native/collection.js +46 -35
- package/lib/error/version.js +4 -2
- package/lib/index.js +12 -7
- package/lib/internal.js +1 -0
- package/lib/model.js +329 -96
- package/lib/plugins/idGetter.js +0 -12
- package/lib/plugins/saveSubdocs.js +1 -1
- package/lib/query.js +202 -109
- package/lib/queryhelpers.js +59 -32
- package/lib/schema/array.js +6 -3
- package/lib/schema/date.js +5 -2
- package/lib/schema/decimal128.js +4 -0
- package/lib/schema/embedded.js +15 -9
- package/lib/schema/index.js +2 -0
- package/lib/schema/map.js +29 -0
- package/lib/schema/objectid.js +0 -20
- package/lib/schema.js +49 -9
- package/lib/schematype.js +19 -1
- package/lib/services/model/applyHooks.js +22 -2
- package/lib/services/model/applyMethods.js +14 -4
- package/lib/services/populate/getVirtual.js +4 -0
- package/lib/services/query/castUpdate.js +1 -1
- package/lib/services/query/completeMany.js +47 -0
- package/lib/types/buffer.js +1 -1
- package/lib/types/documentarray.js +16 -2
- package/lib/types/embedded.js +33 -1
- package/lib/types/index.js +2 -0
- package/lib/types/map.js +149 -0
- package/lib/types/objectid.js +12 -0
- package/lib/types/subdocument.js +30 -1
- package/lib/utils.js +49 -6
- package/migrating_to_5.md +1 -1
- package/package.json +5 -5
package/lib/model.js
CHANGED
|
@@ -11,6 +11,7 @@ var DocumentNotFoundError = require('./error').DocumentNotFoundError;
|
|
|
11
11
|
var DivergentArrayError = require('./error').DivergentArrayError;
|
|
12
12
|
var Error = require('./error');
|
|
13
13
|
var EventEmitter = require('events').EventEmitter;
|
|
14
|
+
var MongooseMap = require('./types/map');
|
|
14
15
|
var OverwriteModelError = require('./error').OverwriteModelError;
|
|
15
16
|
var PromiseProvider = require('./promise_provider');
|
|
16
17
|
var Query = require('./query');
|
|
@@ -35,9 +36,9 @@ var parallelLimit = require('async/parallelLimit');
|
|
|
35
36
|
var setDefaultsOnInsert = require('./services/setDefaultsOnInsert');
|
|
36
37
|
var utils = require('./utils');
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const VERSION_WHERE = 1;
|
|
40
|
+
const VERSION_INC = 2;
|
|
41
|
+
const VERSION_ALL = VERSION_WHERE | VERSION_INC;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Model constructor
|
|
@@ -129,21 +130,27 @@ Model.prototype.baseModelName;
|
|
|
129
130
|
*/
|
|
130
131
|
|
|
131
132
|
Model.prototype.$__handleSave = function(options, callback) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
const _this = this;
|
|
134
|
+
let i;
|
|
135
|
+
let keys;
|
|
136
|
+
let len;
|
|
136
137
|
if (!options.safe && this.schema.options.safe) {
|
|
137
138
|
options.safe = this.schema.options.safe;
|
|
138
139
|
}
|
|
139
140
|
if (typeof options.safe === 'boolean') {
|
|
140
141
|
options.safe = null;
|
|
141
142
|
}
|
|
142
|
-
|
|
143
|
+
let safe = options.safe ? utils.clone(options.safe) : options.safe;
|
|
144
|
+
|
|
145
|
+
const session = 'session' in options ? options.session : this.$session();
|
|
146
|
+
if (session != null) {
|
|
147
|
+
safe = typeof safe === 'object' && safe != null ? safe : {};
|
|
148
|
+
safe.session = session;
|
|
149
|
+
}
|
|
143
150
|
|
|
144
151
|
if (this.isNew) {
|
|
145
152
|
// send entire doc
|
|
146
|
-
|
|
153
|
+
const obj = this.toObject(internalToObjectOptions);
|
|
147
154
|
|
|
148
155
|
if ((obj || {})._id === void 0) {
|
|
149
156
|
// documents must have an _id else mongoose won't know
|
|
@@ -158,7 +165,7 @@ Model.prototype.$__handleSave = function(options, callback) {
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
this.$__version(true, obj);
|
|
161
|
-
this.collection.
|
|
168
|
+
this.collection.insertOne(obj, safe, function(err, ret) {
|
|
162
169
|
if (err) {
|
|
163
170
|
_this.isNew = true;
|
|
164
171
|
_this.emit('isNew', true);
|
|
@@ -181,7 +188,7 @@ Model.prototype.$__handleSave = function(options, callback) {
|
|
|
181
188
|
// since it already exists
|
|
182
189
|
this.$__.inserting = false;
|
|
183
190
|
|
|
184
|
-
|
|
191
|
+
const delta = this.$__delta();
|
|
185
192
|
|
|
186
193
|
if (delta) {
|
|
187
194
|
if (delta instanceof Error) {
|
|
@@ -189,7 +196,7 @@ Model.prototype.$__handleSave = function(options, callback) {
|
|
|
189
196
|
return;
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
|
|
199
|
+
const where = this.$__where(delta[0]);
|
|
193
200
|
if (where instanceof Error) {
|
|
194
201
|
callback(where);
|
|
195
202
|
return;
|
|
@@ -203,7 +210,7 @@ Model.prototype.$__handleSave = function(options, callback) {
|
|
|
203
210
|
}
|
|
204
211
|
}
|
|
205
212
|
|
|
206
|
-
this.collection.
|
|
213
|
+
this.collection.updateOne(where, delta[1], safe, function(err, ret) {
|
|
207
214
|
if (err) {
|
|
208
215
|
callback(err);
|
|
209
216
|
return;
|
|
@@ -234,6 +241,9 @@ Model.prototype.$__save = function(options, callback) {
|
|
|
234
241
|
});
|
|
235
242
|
}
|
|
236
243
|
|
|
244
|
+
// store the modified paths before the document is reset
|
|
245
|
+
const modifiedPaths = this.modifiedPaths();
|
|
246
|
+
|
|
237
247
|
this.$__reset();
|
|
238
248
|
|
|
239
249
|
let numAffected = 0;
|
|
@@ -257,16 +267,17 @@ Model.prototype.$__save = function(options, callback) {
|
|
|
257
267
|
let doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
|
|
258
268
|
this.$__.version = undefined;
|
|
259
269
|
|
|
270
|
+
let key = this.schema.options.versionKey;
|
|
271
|
+
let version = this.getValue(key) || 0;
|
|
272
|
+
|
|
260
273
|
if (numAffected <= 0) {
|
|
261
274
|
// the update failed. pass an error back
|
|
262
|
-
let err = new VersionError(this);
|
|
275
|
+
let err = new VersionError(this, version, modifiedPaths);
|
|
263
276
|
return callback(err);
|
|
264
277
|
}
|
|
265
278
|
|
|
266
279
|
// increment version if was successful
|
|
267
280
|
if (doIncrement) {
|
|
268
|
-
let key = this.schema.options.versionKey;
|
|
269
|
-
let version = this.getValue(key) | 0;
|
|
270
281
|
this.setValue(key, version + 1);
|
|
271
282
|
}
|
|
272
283
|
}
|
|
@@ -321,7 +332,9 @@ Model.prototype.save = function(options, fn) {
|
|
|
321
332
|
options = undefined;
|
|
322
333
|
}
|
|
323
334
|
|
|
324
|
-
if (
|
|
335
|
+
if (options != null) {
|
|
336
|
+
options = utils.clone(options);
|
|
337
|
+
} else {
|
|
325
338
|
options = {};
|
|
326
339
|
}
|
|
327
340
|
|
|
@@ -505,36 +518,40 @@ function handleAtomics(self, where, delta, data, value) {
|
|
|
505
518
|
*/
|
|
506
519
|
|
|
507
520
|
Model.prototype.$__delta = function() {
|
|
508
|
-
|
|
509
|
-
if (!dirty.length && VERSION_ALL !== this.$__.version)
|
|
521
|
+
const dirty = this.$__dirty();
|
|
522
|
+
if (!dirty.length && VERSION_ALL !== this.$__.version) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
510
525
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
526
|
+
let where = {};
|
|
527
|
+
let delta = {};
|
|
528
|
+
const len = dirty.length;
|
|
529
|
+
const divergent = [];
|
|
530
|
+
let d = 0;
|
|
516
531
|
|
|
517
532
|
where._id = this._doc._id;
|
|
518
|
-
|
|
533
|
+
// If `_id` is an object, need to depopulate, but also need to be careful
|
|
534
|
+
// because `_id` can technically be null (see gh-6406)
|
|
535
|
+
if (get(where, '_id.$__', null) != null) {
|
|
519
536
|
where._id = where._id.toObject({ transform: false, depopulate: true });
|
|
520
537
|
}
|
|
521
538
|
|
|
522
539
|
for (; d < len; ++d) {
|
|
523
|
-
|
|
524
|
-
|
|
540
|
+
const data = dirty[d];
|
|
541
|
+
let value = data.value;
|
|
525
542
|
|
|
526
|
-
|
|
543
|
+
const match = checkDivergentArray(this, data.path, value);
|
|
527
544
|
if (match) {
|
|
528
545
|
divergent.push(match);
|
|
529
546
|
continue;
|
|
530
547
|
}
|
|
531
548
|
|
|
532
|
-
|
|
549
|
+
const pop = this.populated(data.path, true);
|
|
533
550
|
if (!pop && this.$__.selected) {
|
|
534
551
|
// If any array was selected using an $elemMatch projection, we alter the path and where clause
|
|
535
552
|
// NOTE: MongoDB only supports projected $elemMatch on top level array.
|
|
536
|
-
|
|
537
|
-
|
|
553
|
+
const pathSplit = data.path.split('.');
|
|
554
|
+
const top = pathSplit[0];
|
|
538
555
|
if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) {
|
|
539
556
|
// If the selected array entry was modified
|
|
540
557
|
if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') {
|
|
@@ -714,7 +731,7 @@ Model.prototype.$__where = function _where(where) {
|
|
|
714
731
|
where._id = this._doc._id;
|
|
715
732
|
}
|
|
716
733
|
|
|
717
|
-
if (this._doc._id
|
|
734
|
+
if (this._doc._id === void 0) {
|
|
718
735
|
return new Error('No _id found on document!');
|
|
719
736
|
}
|
|
720
737
|
|
|
@@ -1064,8 +1081,8 @@ function _ensureIndexes(model, options, callback) {
|
|
|
1064
1081
|
var index = indexes.shift();
|
|
1065
1082
|
if (!index) return done();
|
|
1066
1083
|
|
|
1067
|
-
var indexFields = index[0];
|
|
1068
|
-
var indexOptions = index[1];
|
|
1084
|
+
var indexFields = utils.clone(index[0]);
|
|
1085
|
+
var indexOptions = utils.clone(index[1]);
|
|
1069
1086
|
_handleSafe(options);
|
|
1070
1087
|
|
|
1071
1088
|
indexSingleStart(indexFields, options);
|
|
@@ -1843,6 +1860,126 @@ Model.findByIdAndUpdate = function(id, update, options, callback) {
|
|
|
1843
1860
|
return this.findOneAndUpdate.call(this, {_id: id}, update, options, callback);
|
|
1844
1861
|
};
|
|
1845
1862
|
|
|
1863
|
+
/**
|
|
1864
|
+
* Issue a MongoDB `findOneAndDelete()` command.
|
|
1865
|
+
*
|
|
1866
|
+
* Finds a matching document, removes it, and passes the found document
|
|
1867
|
+
* (if any) to the callback.
|
|
1868
|
+
*
|
|
1869
|
+
* Executes immediately if `callback` is passed else a Query object is returned.
|
|
1870
|
+
*
|
|
1871
|
+
* This function triggers the following middleware.
|
|
1872
|
+
*
|
|
1873
|
+
* - `findOneAndDelete()`
|
|
1874
|
+
*
|
|
1875
|
+
* This function differs slightly from `Model.findOneAndRemove()` in that
|
|
1876
|
+
* `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/),
|
|
1877
|
+
* as opposed to a `findOneAndDelete()` command. For most mongoose use cases,
|
|
1878
|
+
* this distinction is purely pedantic. You should use `findOneAndDelete()`
|
|
1879
|
+
* unless you have a good reason not to.
|
|
1880
|
+
*
|
|
1881
|
+
* ####Options:
|
|
1882
|
+
*
|
|
1883
|
+
* - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
|
|
1884
|
+
* - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
|
|
1885
|
+
* - `select`: sets the document fields to return
|
|
1886
|
+
* - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
|
|
1887
|
+
* - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
|
|
1888
|
+
*
|
|
1889
|
+
* ####Examples:
|
|
1890
|
+
*
|
|
1891
|
+
* A.findOneAndDelete(conditions, options, callback) // executes
|
|
1892
|
+
* A.findOneAndDelete(conditions, options) // return Query
|
|
1893
|
+
* A.findOneAndDelete(conditions, callback) // executes
|
|
1894
|
+
* A.findOneAndDelete(conditions) // returns Query
|
|
1895
|
+
* A.findOneAndDelete() // returns Query
|
|
1896
|
+
*
|
|
1897
|
+
* Values are cast to their appropriate types when using the findAndModify helpers.
|
|
1898
|
+
* However, the below are not executed by default.
|
|
1899
|
+
*
|
|
1900
|
+
* - defaults. Use the `setDefaultsOnInsert` option to override.
|
|
1901
|
+
*
|
|
1902
|
+
* `findAndModify` helpers support limited validation. You can
|
|
1903
|
+
* enable these by setting the `runValidators` options,
|
|
1904
|
+
* respectively.
|
|
1905
|
+
*
|
|
1906
|
+
* If you need full-fledged validation, use the traditional approach of first
|
|
1907
|
+
* retrieving the document.
|
|
1908
|
+
*
|
|
1909
|
+
* Model.findById(id, function (err, doc) {
|
|
1910
|
+
* if (err) ..
|
|
1911
|
+
* doc.name = 'jason bourne';
|
|
1912
|
+
* doc.save(callback);
|
|
1913
|
+
* });
|
|
1914
|
+
*
|
|
1915
|
+
* @param {Object} conditions
|
|
1916
|
+
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
|
|
1917
|
+
* @param {Function} [callback]
|
|
1918
|
+
* @return {Query}
|
|
1919
|
+
* @api public
|
|
1920
|
+
*/
|
|
1921
|
+
|
|
1922
|
+
Model.findOneAndDelete = function(conditions, options, callback) {
|
|
1923
|
+
if (arguments.length === 1 && typeof conditions === 'function') {
|
|
1924
|
+
var msg = 'Model.findOneAndDelete(): First argument must not be a function.\n\n'
|
|
1925
|
+
+ ' ' + this.modelName + '.findOneAndDelete(conditions, callback)\n'
|
|
1926
|
+
+ ' ' + this.modelName + '.findOneAndDelete(conditions)\n'
|
|
1927
|
+
+ ' ' + this.modelName + '.findOneAndDelete()\n';
|
|
1928
|
+
throw new TypeError(msg);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
if (typeof options === 'function') {
|
|
1932
|
+
callback = options;
|
|
1933
|
+
options = undefined;
|
|
1934
|
+
}
|
|
1935
|
+
if (callback) {
|
|
1936
|
+
callback = this.$wrapCallback(callback);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
var fields;
|
|
1940
|
+
if (options) {
|
|
1941
|
+
fields = options.select;
|
|
1942
|
+
options.select = undefined;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var mq = new this.Query({}, {}, this, this.collection);
|
|
1946
|
+
mq.select(fields);
|
|
1947
|
+
|
|
1948
|
+
return mq.findOneAndDelete(conditions, options, callback);
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* Issue a MongoDB `findOneAndDelete()` command by a document's _id field.
|
|
1953
|
+
* In other words, `findByIdAndDelete(id)` is a shorthand for
|
|
1954
|
+
* `findOneAndDelete({ _id: id })`.
|
|
1955
|
+
*
|
|
1956
|
+
* This function triggers the following middleware.
|
|
1957
|
+
*
|
|
1958
|
+
* - `findOneAndDelete()`
|
|
1959
|
+
*
|
|
1960
|
+
* @param {Object|Number|String} id value of `_id` to query by
|
|
1961
|
+
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
|
|
1962
|
+
* @param {Function} [callback]
|
|
1963
|
+
* @return {Query}
|
|
1964
|
+
* @see Model.findOneAndRemove #model_Model.findOneAndRemove
|
|
1965
|
+
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
|
|
1966
|
+
*/
|
|
1967
|
+
|
|
1968
|
+
Model.findByIdAndDelete = function(id, options, callback) {
|
|
1969
|
+
if (arguments.length === 1 && typeof id === 'function') {
|
|
1970
|
+
var msg = 'Model.findByIdAndDelete(): First argument must not be a function.\n\n'
|
|
1971
|
+
+ ' ' + this.modelName + '.findByIdAndDelete(id, callback)\n'
|
|
1972
|
+
+ ' ' + this.modelName + '.findByIdAndDelete(id)\n'
|
|
1973
|
+
+ ' ' + this.modelName + '.findByIdAndDelete()\n';
|
|
1974
|
+
throw new TypeError(msg);
|
|
1975
|
+
}
|
|
1976
|
+
if (callback) {
|
|
1977
|
+
callback = this.$wrapCallback(callback);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
return this.findOneAndDelete({_id: id}, options, callback);
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1846
1983
|
/**
|
|
1847
1984
|
* Issue a mongodb findAndModify remove command.
|
|
1848
1985
|
*
|
|
@@ -2013,15 +2150,15 @@ Model.findByIdAndRemove = function(id, options, callback) {
|
|
|
2013
2150
|
*/
|
|
2014
2151
|
|
|
2015
2152
|
Model.create = function create(doc, callback) {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2153
|
+
let args;
|
|
2154
|
+
let cb;
|
|
2155
|
+
const discriminatorKey = this.schema.options.discriminatorKey;
|
|
2019
2156
|
|
|
2020
2157
|
if (Array.isArray(doc)) {
|
|
2021
2158
|
args = doc;
|
|
2022
2159
|
cb = callback;
|
|
2023
2160
|
} else {
|
|
2024
|
-
|
|
2161
|
+
let last = arguments[arguments.length - 1];
|
|
2025
2162
|
// Handle falsy callbacks re: #5061
|
|
2026
2163
|
if (typeof last === 'function' || !last) {
|
|
2027
2164
|
cb = last;
|
|
@@ -2040,15 +2177,19 @@ Model.create = function create(doc, callback) {
|
|
|
2040
2177
|
return cb(null);
|
|
2041
2178
|
}
|
|
2042
2179
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2180
|
+
const toExecute = [];
|
|
2181
|
+
let firstError;
|
|
2045
2182
|
args.forEach(doc => {
|
|
2046
2183
|
toExecute.push(callback => {
|
|
2047
|
-
|
|
2184
|
+
const Model = this.discriminators && doc[discriminatorKey] != null ?
|
|
2048
2185
|
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this, doc[discriminatorKey]) :
|
|
2049
2186
|
this;
|
|
2050
|
-
|
|
2051
|
-
|
|
2187
|
+
if (Model == null) {
|
|
2188
|
+
throw new Error(`Discriminator "${doc[discriminatorKey]}" not ` +
|
|
2189
|
+
`found for model "${this.modelName}"`);
|
|
2190
|
+
}
|
|
2191
|
+
let toSave = doc;
|
|
2192
|
+
const callbackWrapper = (error, doc) => {
|
|
2052
2193
|
if (error) {
|
|
2053
2194
|
if (!firstError) {
|
|
2054
2195
|
firstError = error;
|
|
@@ -2071,9 +2212,9 @@ Model.create = function create(doc, callback) {
|
|
|
2071
2212
|
});
|
|
2072
2213
|
|
|
2073
2214
|
parallel(toExecute, (error, res) => {
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
for (
|
|
2215
|
+
const savedDocs = [];
|
|
2216
|
+
const len = res.length;
|
|
2217
|
+
for (let i = 0; i < len; ++i) {
|
|
2077
2218
|
if (res[i].doc) {
|
|
2078
2219
|
savedDocs.push(res[i].doc);
|
|
2079
2220
|
}
|
|
@@ -2113,7 +2254,7 @@ Model.create = function create(doc, callback) {
|
|
|
2113
2254
|
*
|
|
2114
2255
|
* @param {Array} [pipeline]
|
|
2115
2256
|
* @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#watch)
|
|
2116
|
-
* @return {ChangeStream} mongoose-specific change stream wrapper
|
|
2257
|
+
* @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
|
|
2117
2258
|
* @api public
|
|
2118
2259
|
*/
|
|
2119
2260
|
|
|
@@ -2121,6 +2262,41 @@ Model.watch = function(pipeline, options) {
|
|
|
2121
2262
|
return new ChangeStream(this, pipeline, options);
|
|
2122
2263
|
};
|
|
2123
2264
|
|
|
2265
|
+
/**
|
|
2266
|
+
* _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
|
|
2267
|
+
* for benefits like causal consistency and [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/).
|
|
2268
|
+
*
|
|
2269
|
+
* This function does not trigger any middleware.
|
|
2270
|
+
*
|
|
2271
|
+
* ####Example:
|
|
2272
|
+
*
|
|
2273
|
+
* const session = await Person.startSession();
|
|
2274
|
+
* let doc = await Person.findOne({ name: 'Ned Stark' }, { session });
|
|
2275
|
+
* await doc.remove();
|
|
2276
|
+
* // `doc` will always be null, even if reading from a replica set
|
|
2277
|
+
* // secondary. Without causal consistency, it is possible to
|
|
2278
|
+
* // get a doc back from the below query if the query reads from a
|
|
2279
|
+
* // secondary that is experiencing replication lag.
|
|
2280
|
+
* doc = await Person.findOne({ name: 'Ned Stark' }, { readPreference: 'secondary' });
|
|
2281
|
+
*
|
|
2282
|
+
* @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
|
|
2283
|
+
* @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
|
|
2284
|
+
* @param {Function} [callback]
|
|
2285
|
+
* @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
|
|
2286
|
+
* @api public
|
|
2287
|
+
*/
|
|
2288
|
+
|
|
2289
|
+
Model.startSession = function(options, callback) {
|
|
2290
|
+
return utils.promiseOrCallback(callback, cb => {
|
|
2291
|
+
if (this.collection.buffer) {
|
|
2292
|
+
return this.collection.addQueue(() => {
|
|
2293
|
+
cb(null, this.db.client.startSession(options));
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
return cb(null, this.db.client.startSession(options));
|
|
2297
|
+
});
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2124
2300
|
/**
|
|
2125
2301
|
* Shortcut for validating an array of documents and inserting them into
|
|
2126
2302
|
* MongoDB if they're all valid. This function is faster than `.create()`
|
|
@@ -2189,7 +2365,9 @@ Model.$__insertMany = function(arr, options, callback) {
|
|
|
2189
2365
|
var validationErrors = [];
|
|
2190
2366
|
arr.forEach(function(doc) {
|
|
2191
2367
|
toExecute.push(function(callback) {
|
|
2192
|
-
doc
|
|
2368
|
+
if (!(doc instanceof _this)) {
|
|
2369
|
+
doc = new _this(doc);
|
|
2370
|
+
}
|
|
2193
2371
|
doc.validate({ __noPromise: true }, function(error) {
|
|
2194
2372
|
if (error) {
|
|
2195
2373
|
// Option `ordered` signals that insert should be continued after reaching
|
|
@@ -2646,8 +2824,10 @@ function _update(model, op, conditions, doc, options, callback) {
|
|
|
2646
2824
|
* ####Example:
|
|
2647
2825
|
*
|
|
2648
2826
|
* var o = {};
|
|
2649
|
-
*
|
|
2650
|
-
*
|
|
2827
|
+
* // `map()` and `reduce()` are run on the MongoDB server, not Node.js,
|
|
2828
|
+
* // these functions are converted to strings
|
|
2829
|
+
* o.map = function () { emit(this.name, 1) };
|
|
2830
|
+
* o.reduce = function (k, vals) { return vals.length };
|
|
2651
2831
|
* User.mapReduce(o, function (err, results) {
|
|
2652
2832
|
* console.log(results)
|
|
2653
2833
|
* })
|
|
@@ -2677,8 +2857,10 @@ function _update(model, op, conditions, doc, options, callback) {
|
|
|
2677
2857
|
* ####Example:
|
|
2678
2858
|
*
|
|
2679
2859
|
* var o = {};
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2860
|
+
* // You can also define `map()` and `reduce()` as strings if your
|
|
2861
|
+
* // linter complains about `emit()` not being defined
|
|
2862
|
+
* o.map = 'function () { emit(this.name, 1) }';
|
|
2863
|
+
* o.reduce = 'function (k, vals) { return vals.length }';
|
|
2682
2864
|
* o.out = { replace: 'createdCollectionNameForResults' }
|
|
2683
2865
|
* o.verbose = true;
|
|
2684
2866
|
*
|
|
@@ -2969,6 +3151,7 @@ Model.geoSearch = function(conditions, options, callback) {
|
|
|
2969
3151
|
*
|
|
2970
3152
|
* @param {Document|Array} docs Either a single document or array of documents to populate.
|
|
2971
3153
|
* @param {Object} options A hash of key/val (path, options) used for population.
|
|
3154
|
+
* @param {Boolean} [options.retainNullValues=false] by default, mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
|
|
2972
3155
|
* @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
|
|
2973
3156
|
* @return {Promise}
|
|
2974
3157
|
* @api public
|
|
@@ -3035,8 +3218,6 @@ const excludeIdReg = /\s?-_id\s?/;
|
|
|
3035
3218
|
const excludeIdRegGlobal = /\s?-_id\s?/g;
|
|
3036
3219
|
|
|
3037
3220
|
function populate(model, docs, options, callback) {
|
|
3038
|
-
var modelsMap;
|
|
3039
|
-
|
|
3040
3221
|
// normalize single / multiple docs passed
|
|
3041
3222
|
if (!Array.isArray(docs)) {
|
|
3042
3223
|
docs = [docs];
|
|
@@ -3046,15 +3227,20 @@ function populate(model, docs, options, callback) {
|
|
|
3046
3227
|
return callback();
|
|
3047
3228
|
}
|
|
3048
3229
|
|
|
3049
|
-
modelsMap = getModelsMapForPopulate(model, docs, options);
|
|
3230
|
+
const modelsMap = getModelsMapForPopulate(model, docs, options);
|
|
3231
|
+
|
|
3050
3232
|
if (modelsMap instanceof Error) {
|
|
3051
3233
|
return utils.immediate(function() {
|
|
3052
3234
|
callback(modelsMap);
|
|
3053
3235
|
});
|
|
3054
3236
|
}
|
|
3055
3237
|
|
|
3056
|
-
|
|
3057
|
-
|
|
3238
|
+
let i;
|
|
3239
|
+
const len = modelsMap.length;
|
|
3240
|
+
let mod;
|
|
3241
|
+
let match;
|
|
3242
|
+
let select;
|
|
3243
|
+
let vals = [];
|
|
3058
3244
|
|
|
3059
3245
|
function flatten(item) {
|
|
3060
3246
|
// no need to include undefined values in our query
|
|
@@ -3073,7 +3259,7 @@ function populate(model, docs, options, callback) {
|
|
|
3073
3259
|
match = {};
|
|
3074
3260
|
}
|
|
3075
3261
|
|
|
3076
|
-
|
|
3262
|
+
let ids = utils.array.flatten(mod.ids, flatten);
|
|
3077
3263
|
ids = utils.array.unique(ids);
|
|
3078
3264
|
|
|
3079
3265
|
if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
|
|
@@ -3247,24 +3433,31 @@ function populate(model, docs, options, callback) {
|
|
|
3247
3433
|
*/
|
|
3248
3434
|
|
|
3249
3435
|
function assignVals(o) {
|
|
3436
|
+
// Glob all options together because `populateOptions` is confusing
|
|
3437
|
+
const retainNullValues = get(o, 'allOptions.options.options.retainNullValues', false);
|
|
3438
|
+
const populateOptions = Object.assign({}, o.options, {
|
|
3439
|
+
justOne: o.justOne,
|
|
3440
|
+
retainNullValues: retainNullValues
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3250
3443
|
// replace the original ids in our intermediate _ids structure
|
|
3251
3444
|
// with the documents found by query
|
|
3252
|
-
assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder,
|
|
3445
|
+
assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions,
|
|
3253
3446
|
o.localField, o.foreignField);
|
|
3254
3447
|
|
|
3255
3448
|
// now update the original documents being populated using the
|
|
3256
3449
|
// result structure that contains real documents.
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3450
|
+
const docs = o.docs;
|
|
3451
|
+
const rawIds = o.rawIds;
|
|
3452
|
+
const options = o.options;
|
|
3260
3453
|
|
|
3261
3454
|
function setValue(val) {
|
|
3262
|
-
return valueFilter(val, options,
|
|
3455
|
+
return valueFilter(val, options, populateOptions);
|
|
3263
3456
|
}
|
|
3264
3457
|
|
|
3265
|
-
for (
|
|
3266
|
-
|
|
3267
|
-
|
|
3458
|
+
for (let i = 0; i < docs.length; ++i) {
|
|
3459
|
+
const existingVal = utils.getValue(o.path, docs[i]);
|
|
3460
|
+
if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
|
|
3268
3461
|
continue;
|
|
3269
3462
|
}
|
|
3270
3463
|
|
|
@@ -3278,10 +3471,27 @@ function assignVals(o) {
|
|
|
3278
3471
|
rawIds[i] = rawIds[i][0];
|
|
3279
3472
|
}
|
|
3280
3473
|
|
|
3474
|
+
|
|
3475
|
+
// If we're populating a map, the existing value will be an object, so
|
|
3476
|
+
// we need to transform again
|
|
3477
|
+
const isMap = docs[i].constructor.name === 'model' ?
|
|
3478
|
+
existingVal instanceof Map :
|
|
3479
|
+
existingVal != null && existingVal.constructor.name === 'Object';
|
|
3480
|
+
if (!o.isVirtual && isMap) {
|
|
3481
|
+
const _keys = existingVal instanceof Map ?
|
|
3482
|
+
Array.from(existingVal.keys()) :
|
|
3483
|
+
Object.keys(existingVal);
|
|
3484
|
+
rawIds[i] = rawIds[i].reduce((cur, v, i) => {
|
|
3485
|
+
// Avoid casting because that causes infinite recursion
|
|
3486
|
+
cur.$init(_keys[i], v);
|
|
3487
|
+
return cur;
|
|
3488
|
+
}, new MongooseMap({}, docs[i]));
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3281
3491
|
if (o.isVirtual && docs[i].constructor.name === 'model') {
|
|
3282
3492
|
// If virtual populate and doc is already init-ed, need to walk through
|
|
3283
3493
|
// the actual doc to set rather than setting `_doc` directly
|
|
3284
|
-
mpath.set(o.path, rawIds[i], docs[i]);
|
|
3494
|
+
mpath.set(o.path, rawIds[i], docs[i], setValue);
|
|
3285
3495
|
} else {
|
|
3286
3496
|
var parts = o.path.split('.');
|
|
3287
3497
|
var cur = docs[i];
|
|
@@ -3294,6 +3504,7 @@ function assignVals(o) {
|
|
|
3294
3504
|
if (docs[i].$__) {
|
|
3295
3505
|
docs[i].populated(o.path, o.allIds[i], o.allOptions);
|
|
3296
3506
|
}
|
|
3507
|
+
|
|
3297
3508
|
utils.setValue(o.path, rawIds[i], docs[i], setValue, false);
|
|
3298
3509
|
}
|
|
3299
3510
|
}
|
|
@@ -3374,9 +3585,6 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, lo
|
|
|
3374
3585
|
// can safely use this to our advantage dealing with sorted
|
|
3375
3586
|
// result sets too.
|
|
3376
3587
|
newOrder.forEach(function(doc, i) {
|
|
3377
|
-
if (!doc) {
|
|
3378
|
-
return;
|
|
3379
|
-
}
|
|
3380
3588
|
rawIds[i] = doc;
|
|
3381
3589
|
});
|
|
3382
3590
|
}
|
|
@@ -3477,6 +3685,7 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3477
3685
|
if (typeof foreignField === 'function') {
|
|
3478
3686
|
foreignField = foreignField.call(doc);
|
|
3479
3687
|
}
|
|
3688
|
+
|
|
3480
3689
|
const ret = convertTo_id(utils.getValue(localField, doc));
|
|
3481
3690
|
const id = String(utils.getValue(foreignField, doc));
|
|
3482
3691
|
options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
|
|
@@ -3539,6 +3748,9 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3539
3748
|
if (schema && schema.caster) {
|
|
3540
3749
|
schema = schema.caster;
|
|
3541
3750
|
}
|
|
3751
|
+
if (schema && schema.$isSchemaMap) {
|
|
3752
|
+
schema = schema.$__schemaType;
|
|
3753
|
+
}
|
|
3542
3754
|
|
|
3543
3755
|
if (!schema && model.discriminators) {
|
|
3544
3756
|
discriminatorKey = model.schema.discriminatorMapping.key;
|
|
@@ -3548,7 +3760,11 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3548
3760
|
|
|
3549
3761
|
if (refPath) {
|
|
3550
3762
|
modelNames = utils.getValue(refPath, doc);
|
|
3551
|
-
isRefPathArray =
|
|
3763
|
+
isRefPathArray = false;
|
|
3764
|
+
if (Array.isArray(modelNames)) {
|
|
3765
|
+
isRefPathArray = true;
|
|
3766
|
+
modelNames = utils.array.flatten(modelNames);
|
|
3767
|
+
}
|
|
3552
3768
|
} else {
|
|
3553
3769
|
if (!modelNameFromQuery) {
|
|
3554
3770
|
var modelForCurrentDoc = model;
|
|
@@ -3627,18 +3843,33 @@ function convertTo_id(val) {
|
|
|
3627
3843
|
if (val instanceof Model) return val._id;
|
|
3628
3844
|
|
|
3629
3845
|
if (Array.isArray(val)) {
|
|
3630
|
-
for (
|
|
3846
|
+
for (let i = 0; i < val.length; ++i) {
|
|
3631
3847
|
if (val[i] instanceof Model) {
|
|
3632
3848
|
val[i] = val[i]._id;
|
|
3633
3849
|
}
|
|
3634
3850
|
}
|
|
3635
|
-
if (val.isMongooseArray) {
|
|
3851
|
+
if (val.isMongooseArray && val._schema) {
|
|
3636
3852
|
return val._schema.cast(val, val._parent);
|
|
3637
3853
|
}
|
|
3638
3854
|
|
|
3639
3855
|
return [].concat(val);
|
|
3640
3856
|
}
|
|
3641
3857
|
|
|
3858
|
+
// `populate('map')` may be an object if populating on a doc that hasn't
|
|
3859
|
+
// been hydrated yet
|
|
3860
|
+
if (val != null && val.constructor.name === 'Object') {
|
|
3861
|
+
const ret = [];
|
|
3862
|
+
for (const key of Object.keys(val)) {
|
|
3863
|
+
ret.push(val[key]);
|
|
3864
|
+
}
|
|
3865
|
+
return ret;
|
|
3866
|
+
}
|
|
3867
|
+
// If doc has already been hydrated, e.g. `doc.populate('map').execPopulate()`
|
|
3868
|
+
// then `val` will already be a map
|
|
3869
|
+
if (val instanceof Map) {
|
|
3870
|
+
return Array.from(val.values());
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3642
3873
|
return val;
|
|
3643
3874
|
}
|
|
3644
3875
|
|
|
@@ -3660,14 +3891,16 @@ function convertTo_id(val) {
|
|
|
3660
3891
|
* that population mapping can occur.
|
|
3661
3892
|
*/
|
|
3662
3893
|
|
|
3663
|
-
function valueFilter(val, assignmentOpts,
|
|
3894
|
+
function valueFilter(val, assignmentOpts, populateOptions) {
|
|
3664
3895
|
if (Array.isArray(val)) {
|
|
3665
3896
|
// find logic
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
for (
|
|
3897
|
+
const ret = [];
|
|
3898
|
+
const numValues = val.length;
|
|
3899
|
+
for (let i = 0; i < numValues; ++i) {
|
|
3669
3900
|
var subdoc = val[i];
|
|
3670
|
-
if (!isDoc(subdoc))
|
|
3901
|
+
if (!isDoc(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) {
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
3671
3904
|
maybeRemoveId(subdoc, assignmentOpts);
|
|
3672
3905
|
ret.push(subdoc);
|
|
3673
3906
|
if (assignmentOpts.originalLimit &&
|
|
@@ -3681,7 +3914,7 @@ function valueFilter(val, assignmentOpts, justOne) {
|
|
|
3681
3914
|
while (val.length > ret.length) {
|
|
3682
3915
|
Array.prototype.pop.apply(val, []);
|
|
3683
3916
|
}
|
|
3684
|
-
for (i = 0; i < ret.length; ++i) {
|
|
3917
|
+
for (let i = 0; i < ret.length; ++i) {
|
|
3685
3918
|
val[i] = ret[i];
|
|
3686
3919
|
}
|
|
3687
3920
|
return val;
|
|
@@ -3693,7 +3926,7 @@ function valueFilter(val, assignmentOpts, justOne) {
|
|
|
3693
3926
|
return val;
|
|
3694
3927
|
}
|
|
3695
3928
|
|
|
3696
|
-
return justOne ? null : [];
|
|
3929
|
+
return populateOptions.justOne ? (val == null ? val : null) : [];
|
|
3697
3930
|
}
|
|
3698
3931
|
|
|
3699
3932
|
/*!
|
|
@@ -3870,24 +4103,24 @@ function applyQueryMiddleware(Query, model) {
|
|
|
3870
4103
|
nullResultByDefault: true
|
|
3871
4104
|
};
|
|
3872
4105
|
|
|
3873
|
-
|
|
3874
|
-
Query.prototype._count, null, kareemOptions);
|
|
4106
|
+
// `update()` thunk has a different name because `_update` was already taken
|
|
3875
4107
|
Query.prototype._execUpdate = model.hooks.createWrapper('update',
|
|
3876
4108
|
Query.prototype._execUpdate, null, kareemOptions);
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4109
|
+
|
|
4110
|
+
[
|
|
4111
|
+
'count',
|
|
4112
|
+
'find',
|
|
4113
|
+
'findOne',
|
|
4114
|
+
'findOneAndDelete',
|
|
4115
|
+
'findOneAndRemove',
|
|
4116
|
+
'findOneAndUpdate',
|
|
4117
|
+
'replaceOne',
|
|
4118
|
+
'updateMany',
|
|
4119
|
+
'updateOne'
|
|
4120
|
+
].forEach(fn => {
|
|
4121
|
+
Query.prototype[`_${fn}`] = model.hooks.createWrapper(fn,
|
|
4122
|
+
Query.prototype[`_${fn}`], null, kareemOptions);
|
|
4123
|
+
});
|
|
3891
4124
|
}
|
|
3892
4125
|
|
|
3893
4126
|
/*!
|