| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 | 
							- var DocumentSource = require("./DocumentSource"),
 
- 	Document = require("../Document"),
 
- 	Expression = require("../expressions/Expression"),
 
- 	Accumulators = require("../accumulators/"),
 
- 	GroupDocumentSource = module.exports = (function(){
 
- 	// CONSTRUCTOR
 
- 	/**
 
- 	 * A class for grouping documents together
 
- 	 * 
 
- 	 * @class GroupDocumentSource
 
- 	 * @namespace munge.pipeline.documentsource
 
- 	 * @module munge
 
- 	 * @constructor
 
- 	 * @param	{ExpressionContext}	
 
- 	**/
 
- 	var klass = module.exports = GroupDocumentSource = function GroupDocumentSource(groupElement){
 
- 		if(!(groupElement instanceof Object && groupElement.constructor.name === "Object") || Object.keys(groupElement).length < 1)
 
- 			throw new Error("a group's fields must be specified in an object");			
 
- 	
 
- 		this.populated = false;
 
- 		this.idExpression = null;
 
- 		this.groups = {}; // GroupsType Value -> Accumulators[]
 
- 		this.groupsKeys = []; // This is to faciliate easier look up of groups
 
- 		this.fieldNames = [];
 
- 		this.accumulatorFactories = [];
 
- 		this.expressions = [];
 
- 		this.currentDocument = null;
 
- 		this.groupCurrentIndex = 0;
 
- 		var groupObj = groupElement[this.getSourceName()];
 
- 		for(var groupFieldName in groupObj){
 
- 			if(groupObj.hasOwnProperty(groupFieldName)){
 
- 				var groupField = groupObj[groupFieldName];
 
- 				
 
- 				if(groupFieldName === "_id"){
 
- 					if(groupField instanceof Object && groupField.constructor.name === "Object"){
 
- 						var objCtx = new Expression.ObjectCtx({isDocumentOk:true});
 
- 						this.idExpression = Expression.parseObject(groupField, objCtx);
 
- 					}else if( typeof groupField === "string"){
 
- 						if(groupField[0] !== "$")
 
- 							this.idExpression = new ConstantExpression(groupField);		
 
- 						var pathString = Expression.removeFieldPrefix(groupField);
 
- 						this.idExpression = new FieldPathExpression(pathString);
 
- 					}else{
 
- 						var typeStr = this._getTypeStr(groupField);
 
- 						switch(typeStr){
 
- 							case "number":
 
- 							case "string":
 
- 							case "boolean":
 
- 							case "object":
 
- 								this.idExpression = new ConstantExpression(groupField);
 
- 								break;
 
- 							default:
 
- 								throw new Error("a group's _id may not include fields of type " + typeStr  + ""); 
 
- 						}
 
- 					}
 
- 				}else{
 
- 					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(this._getTypeStr(groupFieldName) === "object")
 
- 						throw new Error("15951 the group aggregate field '" + groupFieldName + "' must be defined as an expression inside an object");
 
- 					var subFieldCount = 0;
 
- 					for(var subFieldName in groupField){
 
- 						if(groupField.hasOwnProperty(subFieldName)){
 
- 							var subField = groupField[subField],
 
- 								op = DocumentSource.GroupOps[subFieldName];
 
- 							if(!op)
 
- 								throw new Exception("15952 unknown group operator '" + subFieldName + "'");
 
- 							var groupExpression,
 
- 								subFieldTypeStr = this._getTypeStr(subField);
 
- 							if(subFieldTypeStr === "object"){
 
- 								var subFieldObjCtx = new Expression.ObjectCtx({isDocumentOk:true});
 
- 								groupExpression = Expression.parseObject(groupField, subFieldObjCtx);
 
- 							}else if(subFieldTypeStr === "Array"){
 
- 								throw new Exception("15953 aggregating group operators are unary (" + subFieldName + ")");
 
- 							}else{
 
- 								groupExpression = Expression.parseOperand(subField);
 
- 							}
 
- 							this.addAccumulator(groupFieldName,op, groupExpression); 
 
- 							
 
- 							++subFieldCount;
 
- 						}
 
- 					if(subFieldCount != 1)
 
- 						throw new Error("15954 the computed aggregate '" + groupFieldName + "' must specify exactly one operator");
 
- 					}
 
- 				}
 
- 			}
 
- 		}	
 
- 	}, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
- 	klass.GroupOps = {
 
- 		"$addToSet": Accumulators.AddToSet,
 
- 		"$avg": Accumulators.Avg,
 
- 		"$first": Accumulators.First,
 
- 		"$last": Accumulators.Last,
 
- 		"$max": Accumulators.MinMax.bind(null, 1),
 
- 		"$min": Accumulators.MinMax.bind(null, -1),
 
- 		"$push": Accumulators.Push,
 
- 		"$sum": Accumulators.Sum
 
- 	};
 
- 	proto._getTypeStr = function _getTypeStr(obj){
 
- 		var typeofStr=typeof groupField, typeStr = (typeofStr == "object" ? groupField.constructor.name : typeStr);
 
- 		return typeofStr;	
 
- 	};
 
- 	proto.getSourceName = function getSourceName(){
 
- 		return "$group";
 
- 	};
 
- 	proto.advance = function advance(){
 
- 		base.prototype.advance.call(this); // Check for interupts ????
 
- 		if(!this.populated)
 
- 			this.populate();
 
- 		++this.currentGroupsKeysIndex;
 
- 		if(this.currentGroupsKeysIndex === this.groupKeys.length){
 
- 			this.currentDocument = null;
 
- 			return false;	
 
- 		}
 
- 		
 
- 		return true;
 
- 	};
 
- 	proto.eof = function eof(){
 
- 		if(!this.populated)
 
- 			this.populate();
 
- 		
 
- 		return this.currentGroupsKeysIndex === this.groupsKeys.length; 
 
- 	};
 
- 	proto.getCurrent = function getCurrent(){
 
- 		if(!this.populated)
 
- 			this.populate();
 
- 		return this.currentDocument;
 
- 	};
 
- 	
 
- 	proto.addAccumulator = function addAccumulator(fieldName, accumulatorFactory, expression){
 
- 		this.fieldNames.push(fieldName);
 
- 		this.accumulatorFactories.push(accumulatorFactory);
 
- 		this.expressions.push(expression);
 
- 	};
 
- 	proto.populate = function populate(){
 
- 		for(var hasNext = !this.pSource.eof(); hasNext; hasNext = this.pSource.advance()){
 
- 			var currentDocument = this.pSource.getCurrent(),
 
- 				_id = this.idExpression.evaluate(currentDocument) || null,
 
- 				group;
 
- 			if(_id in this.groups){
 
- 				group = this.groups[_id];
 
- 			}else{
 
- 				this.groups[_id] = group = [];
 
- 				this.groupsKeys[this.currentGroupsKeysIndex] = _id;
 
- 				for(var ai =0; ai < this.accumulators.length; ++ai){
 
- 					var accumulator = new this.accumulatorFactories[ai]();
 
- 					accumulators.addOperand(this.expressions[ai]);
 
- 					group.push(accumulator);
 
- 				}
 
- 			}
 
- 			// tickle all the accumulators for the group we found
 
- 			for(var gi=0; gi < group.length; ++gi)
 
- 				group[gi].evaluate(currentDocument);
 
- 		
 
- 			this.currentGroupsKeysIndex = 0; // Start the group
 
- 			if(this.currentGroupsKeysIndex === this.groups.length-1)
 
- 				this.currentDocument = makeDocument(this.currentGroupsKeysIndex);
 
- 			this.populated = true;
 
- 		}
 
- 	};
 
- 	proto.makeDocument = function makeDocument(groupKeyIndex){
 
- 		var groupKey = this.groupKeys[groupKeyIndex],
 
- 			group = this.groups[groupKey],
 
- 			doc = {};
 
- 		doc[Document.ID_PROPERTY_NAME] = groupKey;
 
- 			
 
- 	
 
- 		for(var i = 0; i < this.fieldNames.length; ++i){
 
- 			var fieldName = this.fieldNames[i],
 
- 				value = this.group[i].getValue();
 
- 			if(typeof value !== "undefined"){
 
- 				doc[fieldName] = value;
 
- 			}
 
- 		}
 
- 		return doc;
 
- 	};
 
- 	return klass;
 
- })();
 
 
  |