Skip to content

Commit 06f260b

Browse files
authored
Merge pull request #5771 from Automattic/4.13
4.13
2 parents a48219b + 4348fa1 commit 06f260b

20 files changed

+747
-191
lines changed

docs/middleware.jade

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ block content
66
Middleware (also called pre and post *hooks*) are functions which are passed
77
control during execution of asynchronous functions. Middleware is specified
88
on the schema level and is useful for writing [plugins](./plugins.html).
9-
Mongoose 4.x has 3 types
10-
of middleware: document middleware, model middleware, and query middleware.
9+
Mongoose 4.x has 4 types
10+
of middleware: document middleware, model middleware, aggregate middleware, and query middleware.
1111
Document middleware is supported for the following document functions.
12-
In document middleware functions, `this` refers to the document.
12+
In document middleware functions, `this` refers to the document.
1313

1414
* [init](./api.html#document_Document-init)
1515
* [validate](./api.html#document_Document-validate)
@@ -25,10 +25,16 @@ block content
2525
* [findOneAndRemove](./api.html#query_Query-findOneAndRemove)
2626
* [findOneAndUpdate](./api.html#query_Query-findOneAndUpdate)
2727
* [update](./api.html#query_Query-update)
28-
28+
29+
Aggregate middleware is for `MyModel.aggregate()`. Aggregate middleware
30+
executes when you call `exec()` on an aggregate object.
31+
In aggregate middleware, `this` refers to the [aggregation object](http://mongoosejs.com/docs/api.html#model_Model.aggregate).
32+
33+
* [aggregate](http://mongoosejs.com/docs/api.html#model_Model.aggregate)
34+
2935
Model middleware is supported for the following model functions.
3036
In model middleware functions, `this` refers to the model.
31-
37+
3238
* [insertMany](./api.html#model_Model.insertMany)
3339

3440
All middleware types support pre and post hooks.

lib/aggregate.js

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,41 @@ Aggregate.prototype.append = function() {
106106
return this;
107107
};
108108

109+
/**
110+
* Appends a new $addFields operator to this aggregate pipeline.
111+
* Requires MongoDB v3.4+ to work
112+
*
113+
* ####Examples:
114+
*
115+
* // adding new fields based on existing fields
116+
* aggregate.addFields({
117+
* newField: '$b.nested'
118+
* , plusTen: { $add: ['$val', 10]}
119+
* , sub: {
120+
* name: '$a'
121+
* }
122+
* })
123+
*
124+
* // etc
125+
* aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } });
126+
*
127+
* @param {Object} arg field specification
128+
* @see $addFields https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/
129+
* @return {Aggregate}
130+
* @api public
131+
*/
132+
Aggregate.prototype.addFields = function(arg) {
133+
var fields = {};
134+
if (typeof arg === 'object' && !util.isArray(arg)) {
135+
Object.keys(arg).forEach(function(field) {
136+
fields[field] = arg[field];
137+
});
138+
} else {
139+
throw new Error('Invalid addFields() argument. Must be an object');
140+
}
141+
return this.append({$addFields: fields});
142+
};
143+
109144
/**
110145
* Appends a new $project operator to this aggregate pipeline.
111146
*
@@ -625,26 +660,27 @@ Aggregate.prototype.exec = function(callback) {
625660
throw new Error('Aggregate not bound to any Model');
626661
}
627662
var _this = this;
663+
var model = this._model;
628664
var Promise = PromiseProvider.get();
629-
var options = utils.clone(this.options);
665+
var options = utils.clone(this.options || {});
666+
var pipeline = this._pipeline;
667+
var collection = this._model.collection;
630668

631669
if (options && options.cursor) {
632670
if (options.cursor.async) {
633671
delete options.cursor.async;
634672
return new Promise.ES6(function(resolve) {
635-
if (!_this._model.collection.buffer) {
673+
if (!collection.buffer) {
636674
process.nextTick(function() {
637-
var cursor = _this._model.collection.
638-
aggregate(_this._pipeline, options || {});
675+
var cursor = collection.aggregate(pipeline, options);
639676
decorateCursor(cursor);
640677
resolve(cursor);
641678
callback && callback(null, cursor);
642679
});
643680
return;
644681
}
645-
_this._model.collection.emitter.once('queue', function() {
646-
var cursor = _this._model.collection.
647-
aggregate(_this._pipeline, options || {});
682+
collection.emitter.once('queue', function() {
683+
var cursor = collection.aggregate(pipeline, options);
648684
decorateCursor(cursor);
649685
resolve(cursor);
650686
callback && callback(null, cursor);
@@ -654,14 +690,13 @@ Aggregate.prototype.exec = function(callback) {
654690
delete options.cursor.useMongooseAggCursor;
655691
return new AggregationCursor(this);
656692
}
657-
var cursor = this._model.collection.
658-
aggregate(this._pipeline, this.options || {});
693+
var cursor = collection.aggregate(pipeline, options);
659694
decorateCursor(cursor);
660695
return cursor;
661696
}
662697

663698
return new Promise.ES6(function(resolve, reject) {
664-
if (!_this._pipeline.length) {
699+
if (!pipeline.length) {
665700
var err = new Error('Aggregate has empty pipeline');
666701
if (callback) {
667702
callback(err);
@@ -672,9 +707,20 @@ Aggregate.prototype.exec = function(callback) {
672707

673708
prepareDiscriminatorPipeline(_this);
674709

675-
_this._model
676-
.collection
677-
.aggregate(_this._pipeline, _this.options || {}, function(error, result) {
710+
model.hooks.execPre('aggregate', _this, function(error) {
711+
if (error) {
712+
var _opts = { error: error };
713+
return model.hooks.execPost('aggregate', _this, [null], _opts, function(error) {
714+
if (callback) {
715+
callback(error);
716+
}
717+
reject(error);
718+
});
719+
}
720+
721+
collection.aggregate(pipeline, options, function(error, result) {
722+
var _opts = { error: error };
723+
model.hooks.execPost('aggregate', _this, [result], _opts, function(error, result) {
678724
if (error) {
679725
if (callback) {
680726
callback(error);
@@ -688,6 +734,8 @@ Aggregate.prototype.exec = function(callback) {
688734
}
689735
resolve(result);
690736
});
737+
});
738+
});
691739
});
692740
};
693741

@@ -758,6 +806,8 @@ function isOperator(obj) {
758806
* @param {Aggregate} aggregate Aggregate to prepare
759807
*/
760808

809+
Aggregate._prepareDiscriminatorPipeline = prepareDiscriminatorPipeline;
810+
761811
function prepareDiscriminatorPipeline(aggregate) {
762812
var schema = aggregate._model.schema,
763813
discriminatorMapping = schema && schema.discriminatorMapping;
@@ -781,12 +831,11 @@ function prepareDiscriminatorPipeline(aggregate) {
781831
} else {
782832
var match = {};
783833
match[discriminatorKey] = discriminatorValue;
784-
aggregate._pipeline = [{$match: match}].concat(originalPipeline);
834+
aggregate._pipeline.unshift({ $match: match });
785835
}
786836
}
787837
}
788838

789-
790839
/*!
791840
* Exports
792841
*/

lib/connection.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,28 @@ Connection.prototype._openWithoutPromise = function() {
368368
});
369369
};
370370

371+
/**
372+
* Helper for `createCollection()`. Will explicitly create the given collection
373+
* with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/)
374+
* and [views](https://docs.mongodb.com/manual/core/views/) from mongoose.
375+
*
376+
* Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
377+
*
378+
* @param {string} collection The collection to delete
379+
* @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
380+
* @param {Function} [callback]
381+
* @return {Promise}
382+
* @api public
383+
*/
384+
385+
Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) {
386+
if (typeof options === 'function') {
387+
cb = options;
388+
options = {};
389+
}
390+
this.db.createCollection(collection, options, cb);
391+
});
392+
371393
/**
372394
* Helper for `dropCollection()`. Will delete the given collection, including
373395
* all documents and indexes.
@@ -403,8 +425,10 @@ function _wrapConnHelper(fn) {
403425
return function() {
404426
var _this = this;
405427
var Promise = PromiseProvider.get();
406-
var argsWithoutCb = Array.prototype.slice.call(arguments, 0, fn.length - 1);
407-
var cb = arguments[arguments.length - 1];
428+
var cb = arguments.length > 0 ? arguments[arguments.length - 1] : null;
429+
var argsWithoutCb = typeof cb === 'function' ?
430+
Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
431+
Array.prototype.slice.call(arguments);
408432
var promise = new Promise.ES6(function(resolve, reject) {
409433
if (_this.readyState !== STATES.connected) {
410434
_this.on('open', function() {
@@ -782,6 +806,12 @@ Connection.prototype.openUri = function(uri, options, callback) {
782806
this.user = options.auth.user;
783807
this.pass = options.auth.password;
784808
}
809+
810+
if (options.bufferCommands != null) {
811+
options.bufferMaxEntries = 0;
812+
this.config.bufferCommands = options.bufferCommands;
813+
delete options.bufferCommands;
814+
}
785815
}
786816

787817
this._connectionOptions = options;

lib/cursor/AggregationCursor.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ var util = require('util');
1414
* in addition to several other mechanisms for loading documents from MongoDB
1515
* one at a time.
1616
*
17+
* Creating an AggregationCursor executes the model's pre aggregate hooks,
18+
* but **not** the model's post aggregate hooks.
19+
*
1720
* Unless you're an advanced user, do **not** instantiate this class directly.
1821
* Use [`Aggregate#cursor()`](/docs/api.html#aggregate_Aggregate-cursor) instead.
1922
*
@@ -47,13 +50,17 @@ util.inherits(AggregationCursor, Readable);
4750

4851
function _init(model, c, agg) {
4952
if (!model.collection.buffer) {
50-
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
51-
c.emit('cursor', c.cursor);
52-
} else {
53-
model.collection.emitter.once('queue', function() {
53+
model.hooks.execPre('aggregate', agg, function() {
5454
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
5555
c.emit('cursor', c.cursor);
5656
});
57+
} else {
58+
model.collection.emitter.once('queue', function() {
59+
model.hooks.execPre('aggregate', agg, function() {
60+
c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
61+
c.emit('cursor', c.cursor);
62+
});
63+
});
5764
}
5865
}
5966

0 commit comments

Comments
 (0)