Skip to content

Commit a0aaa82

Browse files
authoredJul 27, 2018
Merge branch 'master' into gh6750
2 parents 8af7c86 + 88457b0 commit a0aaa82

13 files changed

+233
-81
lines changed
 

‎docs/schematypes.jade

+8-8
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ block content
174174
* `trim`: boolean, whether to always call `.trim()` on the value
175175
* `match`: RegExp, creates a [validator](./validation.html) that checks if the value matches the given regular expression
176176
* `enum`: Array, creates a [validator](./validation.html) that checks if the value is in the given array.
177-
* `minlength`: Number, creates a [validator](./validation.html) that checks if the value length is not less then the given number
178-
* `maxlength`: Number, creates a [validator](./validation.html) that checks if the value length is not greater then the given number
177+
* `minlength`: Number, creates a [validator](./validation.html) that checks if the value length is not less than the given number
178+
* `maxlength`: Number, creates a [validator](./validation.html) that checks if the value length is not greater than the given number
179179

180180
<h5>Number</h5>
181181

@@ -266,7 +266,7 @@ block content
266266
console.log(mongoose.Schema.Types.Boolean.convertToFalse);
267267

268268
mongoose.Schema.Types.Boolean.convertToFalse.add('nay');
269-
console.log(new M({ b: 'nay' }).b); // true
269+
console.log(new M({ b: 'nay' }).b); // false
270270
```
271271

272272
<h4 id="arrays">Arrays</h4>
@@ -278,10 +278,10 @@ block content
278278

279279
```javascript
280280
var ToySchema = new Schema({ name: String });
281-
var ToyBox = new Schema({
281+
var ToyBoxSchema = new Schema({
282282
toys: [ToySchema],
283283
buffers: [Buffer],
284-
string: [String],
284+
strings: [String],
285285
numbers: [Number]
286286
// ... etc
287287
});
@@ -290,14 +290,14 @@ block content
290290
Arrays are special because they implicitly have a default value of `[]` (empty array).
291291

292292
```javascript
293-
var Toy = mongoose.model('Test', ToySchema);
294-
console.log((new Toy()).toys); // []
293+
var ToyBox = mongoose.model('ToyBox', ToyBoxSchema);
294+
console.log((new ToyBox()).toys); // []
295295
```
296296

297297
To overwrite this default, you need to set the default value to `undefined`
298298

299299
```javascript
300-
var ToySchema = new Schema({
300+
var ToyBoxSchema = new Schema({
301301
toys: {
302302
type: [ToySchema],
303303
default: undefined

‎docs/transactions.jade

+11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ block content
4646
[require:transactions.*save]
4747
```
4848

49+
## With the Aggregation Framework
50+
51+
The `Model.aggregate()` function also supports transactions. Mongoose
52+
aggregations have a [`session()` helper](/docs/api.html#aggregate_Aggregate-session)
53+
that sets the [`session` option](/docs/api.html#aggregate_Aggregate-option).
54+
Below is an example of executing an aggregation within a transaction.
55+
56+
```javascript
57+
[require:transactions.*aggregate]
58+
```
59+
4960
block append layout
5061
script.
5162
_native.init("CK7DT53U", {

‎lib/aggregate.js

+22
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,27 @@ Aggregate.prototype.hint = function(value) {
633633
return this;
634634
};
635635

636+
/**
637+
* Sets the session for this aggregation. Useful for [transactions](/docs/transactions.html).
638+
*
639+
* ####Example:
640+
*
641+
* const session = await Model.startSession();
642+
* await Model.aggregate(..).session(session);
643+
*
644+
* @param {ClientSession} session
645+
* @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
646+
*/
647+
648+
Aggregate.prototype.session = function(session) {
649+
if (session == null) {
650+
delete this.options.session;
651+
return;
652+
}
653+
this.options.session = session;
654+
return this;
655+
};
656+
636657
/**
637658
* Lets you set arbitrary options, for middleware or plugins.
638659
*
@@ -645,6 +666,7 @@ Aggregate.prototype.hint = function(value) {
645666
* @param [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/)
646667
* @param [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation
647668
* @param [options.collation] object see [`Aggregate.prototype.collation()`](./docs/api.html#aggregate_Aggregate-collation)
669+
* @param [options.session] ClientSession see [`Aggregate.prototype.session()`](./docs/api.html#aggregate_Aggregate-session)
648670
* @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
649671
* @return {Aggregate} this
650672
* @api public

‎lib/connection.js

+7
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ Connection.prototype.openUri = function(uri, options, callback) {
413413
options = null;
414414
}
415415

416+
if (['string', 'number'].indexOf(typeof options) !== -1) {
417+
throw new MongooseError('Mongoose 5.x no longer supports ' +
418+
'`mongoose.connect(host, dbname, port)` or ' +
419+
'`mongoose.createConnection(host, dbname, port)`. See ' +
420+
'http://mongoosejs.com/docs/connections.html for supported connection syntax');
421+
}
422+
416423
const Promise = PromiseProvider.get();
417424
const _this = this;
418425

‎lib/document.js

+11
Original file line numberDiff line numberDiff line change
@@ -2757,6 +2757,17 @@ Document.prototype.populate = function populate() {
27572757
populateOptions.path = nestedPath + '.' + populateOptions.path;
27582758
});
27592759
}
2760+
2761+
// Use `$session()` by default if the document has an associated session
2762+
// See gh-6754
2763+
if (this.$session() != null) {
2764+
const session = this.$session();
2765+
paths.forEach(path => {
2766+
path.options = path.options || {};
2767+
path.options.session = session;
2768+
});
2769+
}
2770+
27602771
topLevelModel.populate(this, paths, fn);
27612772
}
27622773

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
/*!
4+
* Apply query middleware
5+
*
6+
* @param {Query} query constructor
7+
* @param {Model} model
8+
*/
9+
10+
module.exports = function applyQueryMiddleware(Query, model) {
11+
const kareemOptions = {
12+
useErrorHandlers: true,
13+
numCallbackParams: 1,
14+
nullResultByDefault: true
15+
};
16+
17+
// `update()` thunk has a different name because `_update` was already taken
18+
Query.prototype._execUpdate = model.hooks.createWrapper('update',
19+
Query.prototype._execUpdate, null, kareemOptions);
20+
21+
[
22+
'count',
23+
'countDocuments',
24+
'estimatedDocumentCount',
25+
'find',
26+
'findOne',
27+
'findOneAndDelete',
28+
'findOneAndRemove',
29+
'findOneAndUpdate',
30+
'replaceOne',
31+
'updateMany',
32+
'updateOne'
33+
].forEach(fn => {
34+
Query.prototype[`_${fn}`] = model.hooks.createWrapper(fn,
35+
Query.prototype[`_${fn}`], null, kareemOptions);
36+
});
37+
};

‎lib/index.js

-3
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ Mongoose.prototype.get = Mongoose.prototype.set;
158158
* var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
159159
* db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
160160
*
161-
* // with [host, database_name[, port] signature
162-
* db = mongoose.createConnection('localhost', 'database', port)
163-
*
164161
* // and options
165162
* var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
166163
* db = mongoose.createConnection('localhost', 'database', port, opts)

‎lib/model.js

+35-69
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,41 @@
44
* Module dependencies.
55
*/
66

7-
var Aggregate = require('./aggregate');
8-
var ChangeStream = require('./cursor/ChangeStream');
9-
var Document = require('./document');
10-
var DocumentNotFoundError = require('./error').DocumentNotFoundError;
11-
var DivergentArrayError = require('./error').DivergentArrayError;
12-
var Error = require('./error');
13-
var EventEmitter = require('events').EventEmitter;
14-
var MongooseMap = require('./types/map');
15-
var OverwriteModelError = require('./error').OverwriteModelError;
16-
var PromiseProvider = require('./promise_provider');
17-
var Query = require('./query');
18-
var Schema = require('./schema');
19-
var VersionError = require('./error').VersionError;
20-
var ParallelSaveError = require('./error').ParallelSaveError;
21-
var applyHooks = require('./helpers/model/applyHooks');
22-
var applyMethods = require('./helpers/model/applyMethods');
23-
var applyStatics = require('./helpers/model/applyStatics');
24-
var applyWriteConcern = require('./helpers/schema/applyWriteConcern');
25-
var cast = require('./cast');
26-
var castUpdate = require('./helpers/query/castUpdate');
27-
var discriminator = require('./helpers/model/discriminator');
28-
var getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
29-
var immediate = require('./helpers/immediate');
30-
var internalToObjectOptions = require('./options').internalToObjectOptions;
31-
var isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
32-
var get = require('lodash.get');
33-
var getSchemaTypes = require('./helpers/populate/getSchemaTypes');
34-
var getVirtual = require('./helpers/populate/getVirtual');
35-
var modifiedPaths = require('./helpers/update/modifiedPaths');
36-
var mpath = require('mpath');
37-
var parallel = require('async/parallel');
38-
var parallelLimit = require('async/parallelLimit');
39-
var setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert');
40-
var utils = require('./utils');
7+
const Aggregate = require('./aggregate');
8+
const ChangeStream = require('./cursor/ChangeStream');
9+
const Document = require('./document');
10+
const DocumentNotFoundError = require('./error').DocumentNotFoundError;
11+
const DivergentArrayError = require('./error').DivergentArrayError;
12+
const Error = require('./error');
13+
const EventEmitter = require('events').EventEmitter;
14+
const MongooseMap = require('./types/map');
15+
const OverwriteModelError = require('./error').OverwriteModelError;
16+
const PromiseProvider = require('./promise_provider');
17+
const Query = require('./query');
18+
const Schema = require('./schema');
19+
const VersionError = require('./error').VersionError;
20+
const ParallelSaveError = require('./error').ParallelSaveError;
21+
const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
22+
const applyHooks = require('./helpers/model/applyHooks');
23+
const applyMethods = require('./helpers/model/applyMethods');
24+
const applyStatics = require('./helpers/model/applyStatics');
25+
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
26+
const cast = require('./cast');
27+
const castUpdate = require('./helpers/query/castUpdate');
28+
const discriminator = require('./helpers/model/discriminator');
29+
const getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
30+
const immediate = require('./helpers/immediate');
31+
const internalToObjectOptions = require('./options').internalToObjectOptions;
32+
const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
33+
const get = require('lodash.get');
34+
const getSchemaTypes = require('./helpers/populate/getSchemaTypes');
35+
const getVirtual = require('./helpers/populate/getVirtual');
36+
const modifiedPaths = require('./helpers/update/modifiedPaths');
37+
const mpath = require('mpath');
38+
const parallel = require('async/parallel');
39+
const parallelLimit = require('async/parallelLimit');
40+
const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert');
41+
const utils = require('./utils');
4142

4243
const VERSION_WHERE = 1;
4344
const VERSION_INC = 2;
@@ -4380,41 +4381,6 @@ function applyQueryMethods(model, methods) {
43804381
}
43814382
}
43824383

4383-
/*!
4384-
* Apply query middleware
4385-
*
4386-
* @param {Model} model
4387-
*/
4388-
4389-
function applyQueryMiddleware(Query, model) {
4390-
const kareemOptions = {
4391-
useErrorHandlers: true,
4392-
numCallbackParams: 1,
4393-
nullResultByDefault: true
4394-
};
4395-
4396-
// `update()` thunk has a different name because `_update` was already taken
4397-
Query.prototype._execUpdate = model.hooks.createWrapper('update',
4398-
Query.prototype._execUpdate, null, kareemOptions);
4399-
4400-
[
4401-
'count',
4402-
'countDocuments',
4403-
'estimatedDocumentCount',
4404-
'find',
4405-
'findOne',
4406-
'findOneAndDelete',
4407-
'findOneAndRemove',
4408-
'findOneAndUpdate',
4409-
'replaceOne',
4410-
'updateMany',
4411-
'updateOne'
4412-
].forEach(fn => {
4413-
Query.prototype[`_${fn}`] = model.hooks.createWrapper(fn,
4414-
Query.prototype[`_${fn}`], null, kareemOptions);
4415-
});
4416-
}
4417-
44184384
/*!
44194385
* Subclass this model with `conn`, `schema`, and `collection` settings.
44204386
*

‎lib/types/map.js

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ class MongooseMap extends Map {
9090
return new Map(this);
9191
}
9292

93+
toObject() {
94+
return new Map(this);
95+
}
96+
9397
toJSON() {
9498
let ret = {};
9599
const keys = this.keys();

‎lib/utils.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,10 @@ exports.isMongooseObject = function(v) {
482482
return false;
483483
}
484484

485-
return v.$__ != null || v.isMongooseArray || v.isMongooseBuffer;
485+
return v.$__ != null || // Document
486+
v.isMongooseArray || // Array or Document Array
487+
v.isMongooseBuffer || // Buffer
488+
v.$isMongooseMap; // Map
486489
};
487490
var isMongooseObject = exports.isMongooseObject;
488491

‎test/connection.test.js

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ describe('connections:', function() {
5656
}).catch(done);
5757
});
5858

59+
it('throws helpful error with legacy syntax (gh-6756)', function(done) {
60+
assert.throws(function() {
61+
mongoose.createConnection('localhost', 'dbname', 27017);
62+
}, /mongoosejs\.com.*connections\.html/);
63+
done();
64+
});
65+
5966
it('resolving with q (gh-5714)', function(done) {
6067
var bootMongo = Q.defer();
6168

‎test/docs/transactions.test.js

+68
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,72 @@ describe('transactions', function() {
9797
}).
9898
then(doc => assert.ok(doc));
9999
});
100+
101+
it('aggregate', function() {
102+
const Event = db.model('Event', new Schema({ createdAt: Date }), 'Event');
103+
104+
let session = null;
105+
return db.createCollection('Event').
106+
then(() => db.startSession()).
107+
then(_session => {
108+
session = _session;
109+
session.startTransaction();
110+
return Event.insertMany([
111+
{ createdAt: new Date('2018-06-01') },
112+
{ createdAt: new Date('2018-06-02') },
113+
{ createdAt: new Date('2017-06-01') },
114+
{ createdAt: new Date('2017-05-31') }
115+
], { session: session });
116+
}).
117+
then(() => Event.aggregate([
118+
{
119+
$group: {
120+
_id: {
121+
month: { $month: '$createdAt' },
122+
year: { $year: '$createdAt' }
123+
},
124+
count: { $sum: 1 }
125+
}
126+
},
127+
{ $sort: { count: -1, '_id.year': -1, '_id.month': -1 } }
128+
]).session(session)).
129+
then(res => {
130+
assert.deepEqual(res, [
131+
{ _id: { month: 6, year: 2018 }, count: 2 },
132+
{ _id: { month: 6, year: 2017 }, count: 1 },
133+
{ _id: { month: 5, year: 2017 }, count: 1 }
134+
]);
135+
session.commitTransaction();
136+
});
137+
});
138+
139+
it('populate (gh-6754)', function() {
140+
const Author = db.model('Author', new Schema({ name: String }), 'Author');
141+
const Article = db.model('Article', new Schema({
142+
author: {
143+
type: mongoose.Schema.Types.ObjectId,
144+
ref: 'Author'
145+
}
146+
}), 'Article');
147+
148+
let session = null;
149+
return db.createCollection('Author').
150+
then(() => db.createCollection('Article')).
151+
then(() => db.startSession()).
152+
then(_session => {
153+
session = _session;
154+
session.startTransaction();
155+
return Author.create([{ name: 'Val' }], { session: session });
156+
}).
157+
then(authors => Article.create([{ author: authors[0]._id }], { session: session })).
158+
then(articles => Article.findById(articles[0]._id).session(session)).
159+
then(article => {
160+
assert.ok(article.$session());
161+
return article.populate('author').execPopulate();
162+
}).
163+
then(article => {
164+
assert.equal(article.author.name, 'Val');
165+
session.commitTransaction();
166+
});
167+
});
100168
});

‎test/types.map.test.js

+19
Original file line numberDiff line numberDiff line change
@@ -463,4 +463,23 @@ describe('Map', function() {
463463
assert.deepEqual(found.num.toJSON(), { testing: 456 });
464464
});
465465
});
466+
467+
it('updating map doesnt crash (gh-6750)', function() {
468+
return co(function*() {
469+
const Schema = mongoose.Schema;
470+
const User = db.model('gh6750_User', {
471+
maps: { type: Map, of: String, default: {} }
472+
});
473+
474+
const Post = db.model('gh6750_Post', {
475+
user: { type: Schema.Types.ObjectId, ref: 'User' }
476+
});
477+
478+
const user = yield User.create({});
479+
const doc = yield Post.
480+
findOneAndUpdate({}, { user: user }, { upsert: true, new: true });
481+
assert.ok(doc);
482+
assert.equal(doc.user.toHexString(), user._id.toHexString());
483+
});
484+
});
466485
});

0 commit comments

Comments
 (0)
Please sign in to comment.