|  | @@ -21,22 +21,29 @@ var DocumentSource = require("./DocumentSource"),
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  |  var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx) {
 | 
	
		
			
				|  |  |  	if (arguments.length > 1) throw new Error("up to one arg expected");
 | 
	
		
			
				|  |  | +	expCtx = !expCtx ? {} : expCtx;
 | 
	
		
			
				|  |  |  	base.call(this, expCtx);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	this.populated = false;
 | 
	
		
			
				|  |  | -	this.idExpression = null;
 | 
	
		
			
				|  |  | +	this.doingMerge = false;
 | 
	
		
			
				|  |  | +	this.spilled = false;
 | 
	
		
			
				|  |  | +	this.extSortAllowed = expCtx.extSortAllowed && !expCtx.inRouter;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	this.accumulatorFactories = [];
 | 
	
		
			
				|  |  | +	this.currentAccumulators = [];
 | 
	
		
			
				|  |  |  	this.groups = {}; // GroupsType Value -> Accumulators[]
 | 
	
		
			
				|  |  |  	this.groupsKeys = []; // This is to faciliate easier look up of groups
 | 
	
		
			
				|  |  | -	this.originalGroupsKeys = []; // This stores the original group key un-hashed/stringified/whatever
 | 
	
		
			
				|  |  | -	this._variables = null;
 | 
	
		
			
				|  |  | +	this.originalGroupsKeys = [];
 | 
	
		
			
				|  |  | +	this.variables = null;
 | 
	
		
			
				|  |  |  	this.fieldNames = [];
 | 
	
		
			
				|  |  | -	this.accumulatorFactories = [];
 | 
	
		
			
				|  |  | +	this.idFieldNames = [];
 | 
	
		
			
				|  |  |  	this.expressions = [];
 | 
	
		
			
				|  |  | -	this.currentDocument = null;
 | 
	
		
			
				|  |  | +	this.idExpressions = [];
 | 
	
		
			
				|  |  |  	this.currentGroupsKeysIndex = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  }, klass = GroupDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// TODO: Do we need this?
 | 
	
		
			
				|  |  |  klass.groupOps = {
 | 
	
		
			
				|  |  |  	"$addToSet": Accumulators.AddToSet,
 | 
	
		
			
				|  |  |  	"$avg": Accumulators.Avg,
 | 
	
	
		
			
				|  | @@ -72,12 +79,16 @@ proto.getSourceName = function getSourceName() {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * Gets the next document or DocumentSource.EOF if none
 | 
	
		
			
				|  |  | + * Gets the next document or null if none
 | 
	
		
			
				|  |  |   *
 | 
	
		
			
				|  |  |   * @method getNext
 | 
	
		
			
				|  |  |   * @return {Object}
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  |  proto.getNext = function getNext(callback) {
 | 
	
		
			
				|  |  | +	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback.');
 | 
	
		
			
				|  |  | +	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
 | 
	
		
			
				|  |  | +		return callback(new Error("Interrupted"));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	var self = this;
 | 
	
		
			
				|  |  |  	async.series([
 | 
	
		
			
				|  |  |  		function(next) {
 | 
	
	
		
			
				|  | @@ -89,25 +100,25 @@ proto.getNext = function getNext(callback) {
 | 
	
		
			
				|  |  |  				return next();
 | 
	
		
			
				|  |  |  		},
 | 
	
		
			
				|  |  |  		function(next) {
 | 
	
		
			
				|  |  | -			if(Object.keys(self.groups).length === 0) {
 | 
	
		
			
				|  |  | -				return next(null, DocumentSource.EOF);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			//Note: Skipped the spilled logic
 | 
	
		
			
				|  |  | +			// NOTE: Skipped the spilled functionality
 | 
	
		
			
				|  |  | +			if (self.spilled) {
 | 
	
		
			
				|  |  | +				throw new Error("Spilled is not implemented.");
 | 
	
		
			
				|  |  | +			} else {
 | 
	
		
			
				|  |  | +				if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
 | 
	
		
			
				|  |  | +					return next(null, null);
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
 | 
	
		
			
				|  |  | -				return next(null, DocumentSource.EOF);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +				var id = self.originalGroupsKeys[self.currentGroupsKeysIndex],
 | 
	
		
			
				|  |  | +					stringifiedId = self.groupsKeys[self.currentGroupsKeysIndex],
 | 
	
		
			
				|  |  | +					accumulators = self.groups[stringifiedId],
 | 
	
		
			
				|  |  | +					out = self.makeDocument(id, accumulators, self.expCtx.inShard);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			var id = self.groupsKeys[self.currentGroupsKeysIndex],
 | 
	
		
			
				|  |  | -				accumulators = self.groups[id],
 | 
	
		
			
				|  |  | -				out = self.makeDocument(id, accumulators /*,mergeableOutput*/);
 | 
	
		
			
				|  |  | +				if(++self.currentGroupsKeysIndex === self.groupsKeys.length) {
 | 
	
		
			
				|  |  | +					self.dispose();
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if(++self.currentGroupsKeysIndex === self.groupsKeys.length) {
 | 
	
		
			
				|  |  | -				self.dispose();
 | 
	
		
			
				|  |  | +				return next(null, out);
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			return next(null, out);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	], function(err, results) {
 | 
	
		
			
				|  |  |  		callback(err, results[1]);
 | 
	
	
		
			
				|  | @@ -134,8 +145,14 @@ proto.dispose = function dispose() {
 | 
	
		
			
				|  |  |   * @method optimize
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  |  proto.optimize = function optimize() {
 | 
	
		
			
				|  |  | +	// TODO if all _idExpressions are ExpressionConstants after optimization, then we know there
 | 
	
		
			
				|  |  | +	// will only be one group. We should take advantage of that to avoid going through the hash
 | 
	
		
			
				|  |  | +	// table.
 | 
	
		
			
				|  |  |  	var self = this;
 | 
	
		
			
				|  |  | -	self.idExpression = self.idExpression.optimize();
 | 
	
		
			
				|  |  | +	self.idExpressions.forEach(function(expression, i) {
 | 
	
		
			
				|  |  | +		self.idExpressions[i] = expression.optimize();
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	self.expressions.forEach(function(expression, i) {
 | 
	
		
			
				|  |  |  		self.expressions[i] = expression.optimize();
 | 
	
		
			
				|  |  |  	});
 | 
	
	
		
			
				|  | @@ -149,25 +166,38 @@ proto.optimize = function optimize() {
 | 
	
		
			
				|  |  |   * @param explain {Boolean} Create explain output
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  |  proto.serialize = function serialize(explain) {
 | 
	
		
			
				|  |  | -	var insides = {};
 | 
	
		
			
				|  |  | +	var self = this,
 | 
	
		
			
				|  |  | +		insides = {};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	// add the _id
 | 
	
		
			
				|  |  | -	insides._id = this.idExpression.serialize(explain);
 | 
	
		
			
				|  |  | +	if (self.idFieldNames.length === 0) {
 | 
	
		
			
				|  |  | +		if (self.idExpressions.length !== 1) throw new Error("Should only have one _id field");
 | 
	
		
			
				|  |  | +		insides._id = self.idExpressions[0].serialize(explain);
 | 
	
		
			
				|  |  | +	} else {
 | 
	
		
			
				|  |  | +		if (self.idExpressions.length !== self.idFieldNames.length)
 | 
	
		
			
				|  |  | +			throw new Error("Should have the same number of idExpressions and idFieldNames.");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		var md = {};
 | 
	
		
			
				|  |  | +		self.idExpressions.forEach(function(expression, i) {
 | 
	
		
			
				|  |  | +			md[self.idFieldNames[i]] = expression.serialize(explain);
 | 
	
		
			
				|  |  | +		});
 | 
	
		
			
				|  |  | +		insides._id = md;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	//add the remaining fields
 | 
	
		
			
				|  |  | -	var aFacs = this.accumulatorFactories,
 | 
	
		
			
				|  |  | +	var aFacs = self.accumulatorFactories,
 | 
	
		
			
				|  |  |  		aFacLen = aFacs.length;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	for(var i=0; i < aFacLen; i++) {
 | 
	
		
			
				|  |  | -		var aFac = aFacs[i](),
 | 
	
		
			
				|  |  | -			serialExpression = this.expressions[i].serialize(explain), //Get the accumulator's expression
 | 
	
		
			
				|  |  | +		var aFac = new aFacs[i](),
 | 
	
		
			
				|  |  | +			serialExpression = self.expressions[i].serialize(explain), //Get the accumulator's expression
 | 
	
		
			
				|  |  |  			serialAccumulator = {}; //Where we'll put the expression
 | 
	
		
			
				|  |  |  		serialAccumulator[aFac.getOpName()] = serialExpression;
 | 
	
		
			
				|  |  | -		insides[this.fieldNames[i]] = serialAccumulator;
 | 
	
		
			
				|  |  | +		insides[self.fieldNames[i]] = serialAccumulator;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	var serialSource = {};
 | 
	
		
			
				|  |  | -	serialSource[this.getSourceName()] = insides;
 | 
	
		
			
				|  |  | +	serialSource[self.getSourceName()] = insides;
 | 
	
		
			
				|  |  |  	return serialSource;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -192,31 +222,13 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
 | 
	
		
			
				|  |  |  			var groupField = groupObj[groupFieldName];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			if (groupFieldName === "_id") {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  				if(idSet) throw new Error("15948 a group's _id may only be specified once");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				if (groupField instanceof Object && groupField.constructor === Object) {
 | 
	
		
			
				|  |  | -					/*
 | 
	
		
			
				|  |  | -						Use the projection-like set of field paths to create the
 | 
	
		
			
				|  |  | -						group-by key.
 | 
	
		
			
				|  |  | -					*/
 | 
	
		
			
				|  |  | -					var objCtx = new Expression.ObjectCtx({isDocumentOk:true});
 | 
	
		
			
				|  |  | -					group.setIdExpression(Expression.parseObject(groupField, objCtx, vps));
 | 
	
		
			
				|  |  | -					idSet = true;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				} else if (typeof groupField === "string") {
 | 
	
		
			
				|  |  | -					if (groupField[0] === "$") {
 | 
	
		
			
				|  |  | -						group.setIdExpression(FieldPathExpression.parse(groupField, vps));
 | 
	
		
			
				|  |  | -						idSet = true;
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				if (!idSet) {
 | 
	
		
			
				|  |  | -					// constant id - single group
 | 
	
		
			
				|  |  | -					group.setIdExpression(ConstantExpression.create(groupField));
 | 
	
		
			
				|  |  | -					idSet = true;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | +				group.parseIdExpression(groupField, vps);
 | 
	
		
			
				|  |  | +				idSet = true;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +			} 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
 | 
	
	
		
			
				|  | @@ -255,7 +267,7 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (!idSet) throw new Error("15955 a group specification must include an _id");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	group._variables = new Variables(idGenerator.getIdCount());
 | 
	
		
			
				|  |  | +	group.variables = new Variables(idGenerator.getIdCount());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	return group;
 | 
	
		
			
				|  |  |  };
 | 
	
	
		
			
				|  | @@ -269,6 +281,7 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  |  proto.populate = function populate(callback) {
 | 
	
		
			
				|  |  |  	var numAccumulators = this.accumulatorFactories.length;
 | 
	
		
			
				|  |  | +	// NOTE: this is not in mongo, does it belong here?
 | 
	
		
			
				|  |  |  	if(numAccumulators !== this.expressions.length) {
 | 
	
		
			
				|  |  |  		callback(new Error("Must have equal number of accumulators and expressions"));
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -277,34 +290,35 @@ proto.populate = function populate(callback) {
 | 
	
		
			
				|  |  |  		self = this;
 | 
	
		
			
				|  |  |  	async.whilst(
 | 
	
		
			
				|  |  |  		function() {
 | 
	
		
			
				|  |  | -			return input !== DocumentSource.EOF;
 | 
	
		
			
				|  |  | +			return input !== null;
 | 
	
		
			
				|  |  |  		},
 | 
	
		
			
				|  |  |  		function(cb) {
 | 
	
		
			
				|  |  |  			self.source.getNext(function(err, doc) {
 | 
	
		
			
				|  |  |  				if(err) return cb(err);
 | 
	
		
			
				|  |  | -				if(doc === DocumentSource.EOF) {
 | 
	
		
			
				|  |  | +				if(doc === null) {
 | 
	
		
			
				|  |  |  					input = doc;
 | 
	
		
			
				|  |  |  					return cb(); //Need to stop now, no new input
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				input = doc;
 | 
	
		
			
				|  |  | -				self._variables.setRoot(input);
 | 
	
		
			
				|  |  | +				self.variables.setRoot(input);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				/* get the _id value */
 | 
	
		
			
				|  |  | -				var id = self.idExpression.evaluate(self._variables);
 | 
	
		
			
				|  |  | +				var id = self.computeId(self.variables);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				if(undefined === id) id = null;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				var groupKey = JSON.stringify(id),
 | 
	
		
			
				|  |  | -					group = self.groups[JSON.stringify(id)];
 | 
	
		
			
				|  |  | +					group = self.groups[groupKey];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				if(!group) {
 | 
	
		
			
				|  |  | +					self.originalGroupsKeys.push(id);
 | 
	
		
			
				|  |  |  					self.groupsKeys.push(groupKey);
 | 
	
		
			
				|  |  |  					group = [];
 | 
	
		
			
				|  |  |  					self.groups[groupKey] = group;
 | 
	
		
			
				|  |  |  					// Add the accumulators
 | 
	
		
			
				|  |  |  					for(var afi = 0; afi<self.accumulatorFactories.length; afi++) {
 | 
	
		
			
				|  |  | -						group.push(self.accumulatorFactories[afi]());
 | 
	
		
			
				|  |  | +						group.push(new self.accumulatorFactories[afi]());
 | 
	
		
			
				|  |  |  					}
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  				//NOTE: Skipped memory usage stuff for case when group already existed
 | 
	
	
		
			
				|  | @@ -315,11 +329,11 @@ proto.populate = function populate(callback) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				//NOTE: passing the input to each accumulator
 | 
	
		
			
				|  |  |  				for(var gi=0; gi<group.length; gi++) {
 | 
	
		
			
				|  |  | -					group[gi].process(self.expressions[gi].evaluate(self._variables /*, doingMerge*/));
 | 
	
		
			
				|  |  | +					group[gi].process(self.expressions[gi].evaluate(self.variables, self.doingMerge));
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				// We are done with the ROOT document so release it.
 | 
	
		
			
				|  |  | -				self._variables.clearRoot();
 | 
	
		
			
				|  |  | +				self.variables.clearRoot();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				//NOTE: Skipped the part about sorted files
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -336,20 +350,6 @@ proto.populate = function populate(callback) {
 | 
	
		
			
				|  |  |  	);
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * Get the type of something. Handles objects specially to return their true type; i.e. their constructor
 | 
	
		
			
				|  |  | - *
 | 
	
		
			
				|  |  | - * @method populate
 | 
	
		
			
				|  |  | - * @param obj {Object} The object to get the type of
 | 
	
		
			
				|  |  | - * @return {String} The type of the object as a string
 | 
	
		
			
				|  |  | - * @async
 | 
	
		
			
				|  |  | - **/
 | 
	
		
			
				|  |  | -proto._getTypeStr = function _getTypeStr(obj) {
 | 
	
		
			
				|  |  | -	var typeofStr = typeof obj,
 | 
	
		
			
				|  |  | -		typeStr = (typeofStr == "object" && obj !== null) ? obj.constructor.name : typeofStr;
 | 
	
		
			
				|  |  | -	return typeStr;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * Get the dependencies of the group
 | 
	
		
			
				|  |  |   *
 | 
	
	
		
			
				|  | @@ -361,7 +361,9 @@ proto._getTypeStr = function _getTypeStr(obj) {
 | 
	
		
			
				|  |  |  proto.getDependencies = function getDependencies(deps) {
 | 
	
		
			
				|  |  |  	var self = this;
 | 
	
		
			
				|  |  |  	// add _id
 | 
	
		
			
				|  |  | -	this.idExpression.addDependencies(deps);
 | 
	
		
			
				|  |  | +	this.idExpressions.forEach(function(expression, i) {
 | 
	
		
			
				|  |  | +		expression.addDependencies(deps);
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  |  	// add the rest
 | 
	
		
			
				|  |  |  	this.fieldNames.forEach(function (field, i) {
 | 
	
		
			
				|  |  |  		self.expressions[i].addDependencies(deps);
 | 
	
	
		
			
				|  | @@ -392,16 +394,16 @@ proto.addAccumulator = function addAccumulator(fieldName, accumulatorFactory, ex
 | 
	
		
			
				|  |  |   * @param accums {Array} An array of accumulators
 | 
	
		
			
				|  |  |   * @param epxression {Expression} The expression to be evaluated on incoming documents before they are accumulated
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  | -proto.makeDocument = function makeDocument(id, accums /*,mergeableOutput*/) {
 | 
	
		
			
				|  |  | +proto.makeDocument = function makeDocument(id, accums, mergeableOutput) {
 | 
	
		
			
				|  |  |  	var out = {};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* add the _id field */
 | 
	
		
			
				|  |  | -	out._id = id;
 | 
	
		
			
				|  |  | +	out._id = this.expandId(id);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* add the rest of the fields */
 | 
	
		
			
				|  |  |  	this.fieldNames.forEach(function(fieldName, i) {
 | 
	
		
			
				|  |  | -		var val = accums[i].getValue(/*mergeableOutput*/);
 | 
	
		
			
				|  |  | -		if(!val) {
 | 
	
		
			
				|  |  | +		var val = accums[i].getValue(mergeableOutput);
 | 
	
		
			
				|  |  | +		if (!val) {
 | 
	
		
			
				|  |  |  			out[fieldName] = null;
 | 
	
		
			
				|  |  |  		} else {
 | 
	
		
			
				|  |  |  			out[fieldName] = val;
 | 
	
	
		
			
				|  | @@ -412,11 +414,96 @@ proto.makeDocument = function makeDocument(id, accums /*,mergeableOutput*/) {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * Sets the id expression for the group
 | 
	
		
			
				|  |  | + * Computes the internal representation of the group key.
 | 
	
		
			
				|  |  |   *
 | 
	
		
			
				|  |  | - * @method setIdExpression
 | 
	
		
			
				|  |  | - * @param epxression {Expression} The expression to set
 | 
	
		
			
				|  |  | + * @method computeId
 | 
	
		
			
				|  |  | + * @param vars a VariablesParseState
 | 
	
		
			
				|  |  | + * @return vals
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +proto.computeId = function computeId(vars) {
 | 
	
		
			
				|  |  | +	var self = this;
 | 
	
		
			
				|  |  | +	// If only one expression return result directly
 | 
	
		
			
				|  |  | +	if (self.idExpressions.length === 1)
 | 
	
		
			
				|  |  | +		return self.idExpressions[0].evaluate(vars); // NOTE: self will probably need to be async soon
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// Multiple expressions get results wrapped in an array
 | 
	
		
			
				|  |  | +	var vals = [];
 | 
	
		
			
				|  |  | +	self.idExpressions.forEach(function(expression, i) {
 | 
	
		
			
				|  |  | +		vals.push(expression.evaluate(vars));
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return vals;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Converts the internal representation of the group key to the _id shape specified by the
 | 
	
		
			
				|  |  | + * user.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * @method expandId
 | 
	
		
			
				|  |  | + * @param val
 | 
	
		
			
				|  |  | + * @return document representing an id
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +proto.expandId = function expandId(val) {
 | 
	
		
			
				|  |  | +	var self = this;
 | 
	
		
			
				|  |  | +	// _id doesn't get wrapped in a document
 | 
	
		
			
				|  |  | +	if (self.idFieldNames.length === 0)
 | 
	
		
			
				|  |  | +		return val;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	var doc = {};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// _id is a single-field document containing val
 | 
	
		
			
				|  |  | +	if (self.idFieldNames.length === 1) {
 | 
	
		
			
				|  |  | +		doc[self.idFieldNames[0]] = val;
 | 
	
		
			
				|  |  | +		return doc;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// _id is a multi-field document containing the elements of val
 | 
	
		
			
				|  |  | +	val.forEach(function(v, i) {
 | 
	
		
			
				|  |  | +		doc[self.idFieldNames[i]] = v;
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return doc;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Parses the raw id expression into _idExpressions and possibly _idFieldNames.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * @method parseIdExpression
 | 
	
		
			
				|  |  | + * @param groupField {Object} The object with the spec
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +proto.parseIdExpression = function parseIdExpression(groupField, vps) {
 | 
	
		
			
				|  |  | +	var self = this;
 | 
	
		
			
				|  |  | +	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] == '$') {
 | 
	
		
			
				|  |  | +			var objCtx = new Expression.ObjectCtx({});
 | 
	
		
			
				|  |  | +			self.idExpressions.push(Expression.parseObject(idKeyObj, objCtx, vps));
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			Object.keys(idKeyObj).forEach(function(key, i) {
 | 
	
		
			
				|  |  | +				var field = {}; //idKeyObj[key];
 | 
	
		
			
				|  |  | +				field[key] = idKeyObj[key];
 | 
	
		
			
				|  |  | +				self.idFieldNames.push(key);
 | 
	
		
			
				|  |  | +				self.idExpressions.push(Expression.parseOperand(field[key], vps));
 | 
	
		
			
				|  |  | +			});
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	} else if (self._getTypeStr(groupField) === 'string' && groupField[0] === '$') {
 | 
	
		
			
				|  |  | +		self.idExpressions.push(FieldPathExpression.parse(groupField, vps));
 | 
	
		
			
				|  |  | +	} else {
 | 
	
		
			
				|  |  | +		self.idExpressions.push(ConstantExpression.create(groupField));
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Get the type of something. Handles objects specially to return their true type; i.e. their constructor
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * @method _getTypeStr
 | 
	
		
			
				|  |  | + * @param obj {Object} The object to get the type of
 | 
	
		
			
				|  |  | + * @return {String} The type of the object as a string
 | 
	
		
			
				|  |  |   **/
 | 
	
		
			
				|  |  | -proto.setIdExpression = function setIdExpression(expression) {
 | 
	
		
			
				|  |  | -	this.idExpression = expression;
 | 
	
		
			
				|  |  | +proto._getTypeStr = function _getTypeStr(obj) {
 | 
	
		
			
				|  |  | +	var typeofStr = typeof obj,
 | 
	
		
			
				|  |  | +		typeStr = (typeofStr == "object" && obj !== null) ? obj.constructor.name : typeofStr;
 | 
	
		
			
				|  |  | +	return typeStr;
 | 
	
		
			
				|  |  |  };
 |