| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- var DocumentSource = require("./DocumentSource"),
- Document = require("../Document"),
- Expression = require("../expressions/Expression"),
- Accumulators = require("../accumulators/"),
- GroupDocumentSource = module.exports = (function(){
- // CONSTRUCTOR
- /**
- * A base class for all document sources
- * @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;
- })();
|