|
|
@@ -1,7 +1,6 @@
|
|
|
"use strict";
|
|
|
var DocumentSource = require("./DocumentSource"),
|
|
|
- Accumulators = require("../accumulators/"),
|
|
|
- Document = require("../Document"),
|
|
|
+ accumulators = require("../accumulators/"),
|
|
|
Expression = require("../expressions/Expression"),
|
|
|
ConstantExpression = require("../expressions/ConstantExpression"),
|
|
|
FieldPathExpression = require("../expressions/FieldPathExpression"),
|
|
|
@@ -40,20 +39,20 @@ var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx)
|
|
|
this.expressions = [];
|
|
|
this.idExpressions = [];
|
|
|
this.currentGroupsKeysIndex = 0;
|
|
|
-}, klass = GroupDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
|
|
|
+}, klass = GroupDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
|
|
|
|
|
|
klass.isSplittableDocumentSource = true;
|
|
|
|
|
|
// TODO: Do we need this?
|
|
|
klass.groupOps = {
|
|
|
- "$addToSet": Accumulators.AddToSetAccumulator.create,
|
|
|
- "$avg": Accumulators.AvgAccumulator.create,
|
|
|
- "$first": Accumulators.FirstAccumulator.create,
|
|
|
- "$last": Accumulators.LastAccumulator.create,
|
|
|
- "$max": Accumulators.MinMaxAccumulator.createMax, // $min and $max have special constructors because they share base features
|
|
|
- "$min": Accumulators.MinMaxAccumulator.createMin,
|
|
|
- "$push": Accumulators.PushAccumulator.create,
|
|
|
- "$sum": Accumulators.SumAccumulator.create,
|
|
|
+ "$addToSet": accumulators.AddToSetAccumulator.create,
|
|
|
+ "$avg": accumulators.AvgAccumulator.create,
|
|
|
+ "$first": accumulators.FirstAccumulator.create,
|
|
|
+ "$last": accumulators.LastAccumulator.create,
|
|
|
+ "$max": accumulators.MinMaxAccumulator.createMax, // $min and $max have special constructors because they share base features
|
|
|
+ "$min": accumulators.MinMaxAccumulator.createMin,
|
|
|
+ "$push": accumulators.PushAccumulator.create,
|
|
|
+ "$sum": accumulators.SumAccumulator.create,
|
|
|
};
|
|
|
|
|
|
klass.groupName = "$group";
|
|
|
@@ -86,7 +85,7 @@ proto.getSourceName = function getSourceName() {
|
|
|
* @return {Object}
|
|
|
**/
|
|
|
proto.getNext = function getNext(callback) {
|
|
|
- if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback.');
|
|
|
+ if (!callback) throw new Error(this.getSourceName() + " #getNext() requires callback.");
|
|
|
if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
|
|
|
return callback(new Error("Interrupted"));
|
|
|
|
|
|
@@ -108,13 +107,13 @@ proto.getNext = function getNext(callback) {
|
|
|
if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
return next(null, null);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var out;
|
|
|
try {
|
|
|
var id = self.originalGroupsKeys[self.currentGroupsKeysIndex],
|
|
|
stringifiedId = self.groupsKeys[self.currentGroupsKeysIndex],
|
|
|
accumulators = self.groups[stringifiedId];
|
|
|
-
|
|
|
+
|
|
|
out = self.makeDocument(id, accumulators, self.expCtx.inShard);
|
|
|
|
|
|
if(++self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
@@ -138,7 +137,7 @@ proto.getNext = function getNext(callback) {
|
|
|
* @method dispose
|
|
|
**/
|
|
|
proto.dispose = function dispose() {
|
|
|
- //NOTE: Skipped 'freeing' our resources; at best we could remove some references to things, but our parent will probably forget us anyways!
|
|
|
+ //NOTE: Skipped 'freeing' our resources; at best we could remove some refs
|
|
|
|
|
|
// make us look done
|
|
|
this.currentGroupsKeysIndex = this.groupsKeys.length;
|
|
|
@@ -214,7 +213,7 @@ proto.serialize = function serialize(explain) {
|
|
|
* @method createFromJson
|
|
|
* @param elem {Object} The group specification object; the right hand side of the $group
|
|
|
**/
|
|
|
-klass.createFromJson = function createFromJson(elem, expCtx) {
|
|
|
+klass.createFromJson = function createFromJson(elem, expCtx) { //jshint maxcomplexity:17
|
|
|
if (!(elem instanceof Object && elem.constructor === Object)) throw new Error("a group's fields must be specified in an object");
|
|
|
|
|
|
var group = GroupDocumentSource.create(expCtx),
|
|
|
@@ -234,16 +233,22 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
|
|
|
group.parseIdExpression(groupField, vps);
|
|
|
idSet = true;
|
|
|
|
|
|
- } else if (groupFieldName === '$doingMerge' && groupField) {
|
|
|
+ } else if (groupFieldName === "$doingMerge" && groupField) {
|
|
|
throw new Error("17030 $doingMerge should be true if present");
|
|
|
} else {
|
|
|
/*
|
|
|
Treat as a projection field with the additional ability to
|
|
|
add aggregation operators.
|
|
|
*/
|
|
|
- if (groupFieldName.indexOf(".") !== -1) throw new Error("16414 the group aggregate field name '" + groupFieldName + "' cannot contain '.'");
|
|
|
- if (groupFieldName[0] === "$") throw new Error("15950 the group aggregate field name '" + groupFieldName + "' cannot be an operator name");
|
|
|
- if (group._getTypeStr(groupFieldName) === "Object") throw new Error("15951 the group aggregate field '" + groupFieldName + "' must be defined as an expression inside an object");
|
|
|
+ if (groupFieldName.indexOf(".") !== -1)
|
|
|
+ throw new Error("the group aggregate field name '" + groupFieldName +
|
|
|
+ "' cannot be used because $group's field names cannot contain '.'; uassert code 16414");
|
|
|
+ if (groupFieldName[0] === "$")
|
|
|
+ throw new Error("the group aggregate field name '" +
|
|
|
+ groupFieldName + "' cannot be an operator name; uassert 15950");
|
|
|
+ if (group._getTypeStr(groupFieldName) === "Object")
|
|
|
+ throw new Error("the group aggregate field '" + groupFieldName +
|
|
|
+ "' must be defined as an expression inside an object; uassert 15951");
|
|
|
|
|
|
var subElementCount = 0;
|
|
|
for (var subElementName in groupField) {
|
|
|
@@ -267,7 +272,9 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
|
|
|
++subElementCount;
|
|
|
}
|
|
|
}
|
|
|
- if (subElementCount !== 1) throw new Error("15954 the computed aggregate '" + groupFieldName + "' must specify exactly one operator");
|
|
|
+ if (subElementCount !== 1)
|
|
|
+ throw new Error("the computed aggregate '" +
|
|
|
+ groupFieldName + "' must specify exactly one operator; uassert code 15954");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -331,7 +338,7 @@ proto.populate = function populate(callback) {
|
|
|
//NOTE: Skipped memory usage stuff for case when group already existed
|
|
|
|
|
|
if(numAccumulators !== group.length) {
|
|
|
- throw new Error('Group must have one of each accumulator');
|
|
|
+ throw new Error("Group must have one of each accumulator");
|
|
|
}
|
|
|
|
|
|
//NOTE: passing the input to each accumulator
|
|
|
@@ -482,11 +489,11 @@ proto.expandId = function expandId(val) {
|
|
|
*/
|
|
|
proto.parseIdExpression = function parseIdExpression(groupField, vps) {
|
|
|
var self = this;
|
|
|
- if (self._getTypeStr(groupField) === 'Object' && Object.keys(groupField).length !== 0) {
|
|
|
+ if (self._getTypeStr(groupField) === "Object" && Object.keys(groupField).length !== 0) {
|
|
|
// {_id: {}} is treated as grouping on a constant, not an expression
|
|
|
|
|
|
var idKeyObj = groupField;
|
|
|
- if (Object.keys(idKeyObj)[0][0] == '$') {
|
|
|
+ if (Object.keys(idKeyObj)[0][0] === "$") {
|
|
|
var objCtx = new Expression.ObjectCtx({});
|
|
|
self.idExpressions.push(Expression.parseObject(idKeyObj, objCtx, vps));
|
|
|
} else {
|
|
|
@@ -497,7 +504,7 @@ proto.parseIdExpression = function parseIdExpression(groupField, vps) {
|
|
|
self.idExpressions.push(Expression.parseOperand(field[key], vps));
|
|
|
});
|
|
|
}
|
|
|
- } else if (self._getTypeStr(groupField) === 'string' && groupField[0] === '$') {
|
|
|
+ } else if (self._getTypeStr(groupField) === "string" && groupField[0] === "$") {
|
|
|
self.idExpressions.push(FieldPathExpression.parse(groupField, vps));
|
|
|
} else {
|
|
|
self.idExpressions.push(ConstantExpression.create(groupField));
|
|
|
@@ -513,7 +520,7 @@ proto.parseIdExpression = function parseIdExpression(groupField, vps) {
|
|
|
**/
|
|
|
proto._getTypeStr = function _getTypeStr(obj) {
|
|
|
var typeofStr = typeof obj,
|
|
|
- typeStr = (typeofStr == "object" && obj !== null) ? obj.constructor.name : typeofStr;
|
|
|
+ typeStr = (typeofStr === "object" && obj !== null) ? obj.constructor.name : typeofStr;
|
|
|
return typeStr;
|
|
|
};
|
|
|
|
|
|
@@ -529,8 +536,12 @@ proto.getMergeSource = function getMergeSource() {
|
|
|
vps = new VariablesParseState(idGenerator);
|
|
|
|
|
|
merger.idExpressions.push(FieldPathExpression.parse("$$ROOT._id", vps));
|
|
|
+
|
|
|
for (var i = 0; i < self.fieldNames.length; i++) {
|
|
|
- merger.addAccumulator(self.fieldNames[i], self.accumulatorFactories[i], FieldPathExpression.create("$$ROOT." + self.fieldNames[i], vps));
|
|
|
+ merger.addAccumulator(
|
|
|
+ self.fieldNames[i], self.accumulatorFactories[i],
|
|
|
+ FieldPathExpression.create("$$ROOT." + self.fieldNames[i], vps)
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
return merger;
|