|
@@ -21,22 +21,29 @@ var DocumentSource = require("./DocumentSource"),
|
|
|
**/
|
|
**/
|
|
|
var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx) {
|
|
var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx) {
|
|
|
if (arguments.length > 1) throw new Error("up to one arg expected");
|
|
if (arguments.length > 1) throw new Error("up to one arg expected");
|
|
|
|
|
+ expCtx = !expCtx ? {} : expCtx;
|
|
|
base.call(this, expCtx);
|
|
base.call(this, expCtx);
|
|
|
|
|
|
|
|
this.populated = false;
|
|
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.groups = {}; // GroupsType Value -> Accumulators[]
|
|
|
this.groupsKeys = []; // This is to faciliate easier look up of groups
|
|
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.fieldNames = [];
|
|
|
- this.accumulatorFactories = [];
|
|
|
|
|
|
|
+ this.idFieldNames = [];
|
|
|
this.expressions = [];
|
|
this.expressions = [];
|
|
|
- this.currentDocument = null;
|
|
|
|
|
|
|
+ this.idExpressions = [];
|
|
|
this.currentGroupsKeysIndex = 0;
|
|
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}});
|
|
|
|
|
|
|
|
|
|
+// TODO: Do we need this?
|
|
|
klass.groupOps = {
|
|
klass.groupOps = {
|
|
|
"$addToSet": Accumulators.AddToSet,
|
|
"$addToSet": Accumulators.AddToSet,
|
|
|
"$avg": Accumulators.Avg,
|
|
"$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
|
|
* @method getNext
|
|
|
* @return {Object}
|
|
* @return {Object}
|
|
|
**/
|
|
**/
|
|
|
proto.getNext = function getNext(callback) {
|
|
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;
|
|
var self = this;
|
|
|
async.series([
|
|
async.series([
|
|
|
function(next) {
|
|
function(next) {
|
|
@@ -89,25 +100,25 @@ proto.getNext = function getNext(callback) {
|
|
|
return next();
|
|
return next();
|
|
|
},
|
|
},
|
|
|
function(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) {
|
|
], function(err, results) {
|
|
|
callback(err, results[1]);
|
|
callback(err, results[1]);
|
|
@@ -134,8 +145,14 @@ proto.dispose = function dispose() {
|
|
|
* @method optimize
|
|
* @method optimize
|
|
|
**/
|
|
**/
|
|
|
proto.optimize = function 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;
|
|
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.forEach(function(expression, i) {
|
|
|
self.expressions[i] = expression.optimize();
|
|
self.expressions[i] = expression.optimize();
|
|
|
});
|
|
});
|
|
@@ -149,25 +166,38 @@ proto.optimize = function optimize() {
|
|
|
* @param explain {Boolean} Create explain output
|
|
* @param explain {Boolean} Create explain output
|
|
|
**/
|
|
**/
|
|
|
proto.serialize = function serialize(explain) {
|
|
proto.serialize = function serialize(explain) {
|
|
|
- var insides = {};
|
|
|
|
|
|
|
+ var self = this,
|
|
|
|
|
+ insides = {};
|
|
|
|
|
|
|
|
// add the _id
|
|
// 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
|
|
//add the remaining fields
|
|
|
- var aFacs = this.accumulatorFactories,
|
|
|
|
|
|
|
+ var aFacs = self.accumulatorFactories,
|
|
|
aFacLen = aFacs.length;
|
|
aFacLen = aFacs.length;
|
|
|
|
|
|
|
|
for(var i=0; i < aFacLen; i++) {
|
|
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 = {}; //Where we'll put the expression
|
|
|
serialAccumulator[aFac.getOpName()] = serialExpression;
|
|
serialAccumulator[aFac.getOpName()] = serialExpression;
|
|
|
- insides[this.fieldNames[i]] = serialAccumulator;
|
|
|
|
|
|
|
+ insides[self.fieldNames[i]] = serialAccumulator;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var serialSource = {};
|
|
var serialSource = {};
|
|
|
- serialSource[this.getSourceName()] = insides;
|
|
|
|
|
|
|
+ serialSource[self.getSourceName()] = insides;
|
|
|
return serialSource;
|
|
return serialSource;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -192,31 +222,13 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
|
|
|
var groupField = groupObj[groupFieldName];
|
|
var groupField = groupObj[groupFieldName];
|
|
|
|
|
|
|
|
if (groupFieldName === "_id") {
|
|
if (groupFieldName === "_id") {
|
|
|
-
|
|
|
|
|
if(idSet) throw new Error("15948 a group's _id may only be specified once");
|
|
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 {
|
|
} else {
|
|
|
/*
|
|
/*
|
|
|
Treat as a projection field with the additional ability to
|
|
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");
|
|
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;
|
|
return group;
|
|
|
};
|
|
};
|
|
@@ -269,6 +281,7 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
|
|
|
**/
|
|
**/
|
|
|
proto.populate = function populate(callback) {
|
|
proto.populate = function populate(callback) {
|
|
|
var numAccumulators = this.accumulatorFactories.length;
|
|
var numAccumulators = this.accumulatorFactories.length;
|
|
|
|
|
+ // NOTE: this is not in mongo, does it belong here?
|
|
|
if(numAccumulators !== this.expressions.length) {
|
|
if(numAccumulators !== this.expressions.length) {
|
|
|
callback(new Error("Must have equal number of accumulators and expressions"));
|
|
callback(new Error("Must have equal number of accumulators and expressions"));
|
|
|
}
|
|
}
|
|
@@ -277,34 +290,35 @@ proto.populate = function populate(callback) {
|
|
|
self = this;
|
|
self = this;
|
|
|
async.whilst(
|
|
async.whilst(
|
|
|
function() {
|
|
function() {
|
|
|
- return input !== DocumentSource.EOF;
|
|
|
|
|
|
|
+ return input !== null;
|
|
|
},
|
|
},
|
|
|
function(cb) {
|
|
function(cb) {
|
|
|
self.source.getNext(function(err, doc) {
|
|
self.source.getNext(function(err, doc) {
|
|
|
if(err) return cb(err);
|
|
if(err) return cb(err);
|
|
|
- if(doc === DocumentSource.EOF) {
|
|
|
|
|
|
|
+ if(doc === null) {
|
|
|
input = doc;
|
|
input = doc;
|
|
|
return cb(); //Need to stop now, no new input
|
|
return cb(); //Need to stop now, no new input
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
input = doc;
|
|
input = doc;
|
|
|
- self._variables.setRoot(input);
|
|
|
|
|
|
|
+ self.variables.setRoot(input);
|
|
|
|
|
|
|
|
/* get the _id value */
|
|
/* get the _id value */
|
|
|
- var id = self.idExpression.evaluate(self._variables);
|
|
|
|
|
|
|
+ var id = self.computeId(self.variables);
|
|
|
|
|
|
|
|
if(undefined === id) id = null;
|
|
if(undefined === id) id = null;
|
|
|
|
|
|
|
|
var groupKey = JSON.stringify(id),
|
|
var groupKey = JSON.stringify(id),
|
|
|
- group = self.groups[JSON.stringify(id)];
|
|
|
|
|
|
|
+ group = self.groups[groupKey];
|
|
|
|
|
|
|
|
if(!group) {
|
|
if(!group) {
|
|
|
|
|
+ self.originalGroupsKeys.push(id);
|
|
|
self.groupsKeys.push(groupKey);
|
|
self.groupsKeys.push(groupKey);
|
|
|
group = [];
|
|
group = [];
|
|
|
self.groups[groupKey] = group;
|
|
self.groups[groupKey] = group;
|
|
|
// Add the accumulators
|
|
// Add the accumulators
|
|
|
for(var afi = 0; afi<self.accumulatorFactories.length; afi++) {
|
|
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
|
|
//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
|
|
//NOTE: passing the input to each accumulator
|
|
|
for(var gi=0; gi<group.length; gi++) {
|
|
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.
|
|
// We are done with the ROOT document so release it.
|
|
|
- self._variables.clearRoot();
|
|
|
|
|
|
|
+ self.variables.clearRoot();
|
|
|
|
|
|
|
|
//NOTE: Skipped the part about sorted files
|
|
//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
|
|
* Get the dependencies of the group
|
|
|
*
|
|
*
|
|
@@ -361,7 +361,9 @@ proto._getTypeStr = function _getTypeStr(obj) {
|
|
|
proto.getDependencies = function getDependencies(deps) {
|
|
proto.getDependencies = function getDependencies(deps) {
|
|
|
var self = this;
|
|
var self = this;
|
|
|
// add _id
|
|
// add _id
|
|
|
- this.idExpression.addDependencies(deps);
|
|
|
|
|
|
|
+ this.idExpressions.forEach(function(expression, i) {
|
|
|
|
|
+ expression.addDependencies(deps);
|
|
|
|
|
+ });
|
|
|
// add the rest
|
|
// add the rest
|
|
|
this.fieldNames.forEach(function (field, i) {
|
|
this.fieldNames.forEach(function (field, i) {
|
|
|
self.expressions[i].addDependencies(deps);
|
|
self.expressions[i].addDependencies(deps);
|
|
@@ -392,16 +394,16 @@ proto.addAccumulator = function addAccumulator(fieldName, accumulatorFactory, ex
|
|
|
* @param accums {Array} An array of accumulators
|
|
* @param accums {Array} An array of accumulators
|
|
|
* @param epxression {Expression} The expression to be evaluated on incoming documents before they are accumulated
|
|
* @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 = {};
|
|
var out = {};
|
|
|
|
|
|
|
|
/* add the _id field */
|
|
/* add the _id field */
|
|
|
- out._id = id;
|
|
|
|
|
|
|
+ out._id = this.expandId(id);
|
|
|
|
|
|
|
|
/* add the rest of the fields */
|
|
/* add the rest of the fields */
|
|
|
this.fieldNames.forEach(function(fieldName, i) {
|
|
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;
|
|
out[fieldName] = null;
|
|
|
} else {
|
|
} else {
|
|
|
out[fieldName] = val;
|
|
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;
|
|
|
};
|
|
};
|