|
@@ -23,6 +23,8 @@ 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");
|
|
|
base.call(this, expCtx);
|
|
base.call(this, expCtx);
|
|
|
|
|
|
|
|
|
|
+ expCtx = !expCtx ? {} : expCtx;
|
|
|
|
|
+
|
|
|
this.populated = false;
|
|
this.populated = false;
|
|
|
this._doingMerge = false;
|
|
this._doingMerge = false;
|
|
|
this._spilled = false;
|
|
this._spilled = false;
|
|
@@ -31,17 +33,14 @@ var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx)
|
|
|
|
|
|
|
|
this.accumulatorFactories = [];
|
|
this.accumulatorFactories = [];
|
|
|
this.currentAccumulators = [];
|
|
this.currentAccumulators = [];
|
|
|
-
|
|
|
|
|
- // TODO: the rest of this may be bogus
|
|
|
|
|
- // this.idExpression = null;
|
|
|
|
|
- // 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.fieldNames = [];
|
|
|
|
|
- // this.expressions = [];
|
|
|
|
|
- // this.currentDocument = null;
|
|
|
|
|
- // this.currentGroupsKeysIndex = 0;
|
|
|
|
|
|
|
+ this.groups = {}; // GroupsType Value -> Accumulators[]
|
|
|
|
|
+ this.groupsKeys = []; // This is to faciliate easier look up of groups
|
|
|
|
|
+ this._variables = null;
|
|
|
|
|
+ this.fieldNames = [];
|
|
|
|
|
+ this.idFieldNames = [];
|
|
|
|
|
+ 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}});
|
|
|
|
|
|
|
@@ -87,62 +86,11 @@ proto.getSourceName = function getSourceName() {
|
|
|
* @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)
|
|
if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
|
|
|
- return callback(new Error("EOF")):
|
|
|
|
|
|
|
+ return callback(new Error("Interrupted"));
|
|
|
|
|
|
|
|
var self = this;
|
|
var self = this;
|
|
|
- if (!this.populated)
|
|
|
|
|
- this.populate();
|
|
|
|
|
-
|
|
|
|
|
- if (this._spilled) {
|
|
|
|
|
- if (!this._sortIterator)
|
|
|
|
|
- return callback(null, undefined); // this is a boost::none in mongo
|
|
|
|
|
-
|
|
|
|
|
- numAccumulators = this.accumulatorFactories.length; // TODO: whaaat?
|
|
|
|
|
- for (var i = 0; i < numAccumulators; i++) {
|
|
|
|
|
- this.currentAccumulators[i].reset(); // prep accumulatorts for new group
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this._currentId = this.firstPartOfNextGroup.first;
|
|
|
|
|
- while (this._currentId === _firstPartOfNextGroup.first) {
|
|
|
|
|
- // Inside of this loop, _firstPartOfNextGroup is the current data being processed.
|
|
|
|
|
- // At loop exit, it is the first value to be processed in the next group.
|
|
|
|
|
-
|
|
|
|
|
- switch (numAccumulators) { // mirrors switch in spill()
|
|
|
|
|
- case 0: // No Accumulators so no Values
|
|
|
|
|
- break;
|
|
|
|
|
- case 1: // single accumulators serialize as a single value
|
|
|
|
|
- this.currentAccumulators[0].process(_firstPartOfNextGroup.second, /*merging=*/true);
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- var accumulatorStates = _firstPartOfNextGroup.second.getArray();
|
|
|
|
|
- for (var j = 0; j < numAccumulators; j++) {
|
|
|
|
|
- this.currentAccumulators[j].process(accumulatorStates[j], /*merging=*/true);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!this._sorterIterator.more()) {
|
|
|
|
|
- dispose(); // what?
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- _firstPartOfNextGroup = _sorterIterator.next();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return callback(null, makeDocument(_currentId, _currentAccumulators, this.expCtx.inShard));
|
|
|
|
|
- } else {
|
|
|
|
|
- if (groups.length === 0)
|
|
|
|
|
- return callback(null, undefined); // this is a boost::none in mongo
|
|
|
|
|
-
|
|
|
|
|
- var out = makeDocument(groupsIterator[0], groupsIterator[1], expCtx.inShard);
|
|
|
|
|
-
|
|
|
|
|
- if (++groupsIterator === groups.end)
|
|
|
|
|
- dispose();
|
|
|
|
|
-
|
|
|
|
|
- return callback(null, out);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
async.series([
|
|
async.series([
|
|
|
function(next) {
|
|
function(next) {
|
|
|
if (!self.populated)
|
|
if (!self.populated)
|
|
@@ -153,25 +101,71 @@ 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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (self._spilled) {
|
|
|
|
|
+ // NOTE: this got skipped before, work on it
|
|
|
|
|
+ throw new error("Spilled isn't finished.")
|
|
|
|
|
+ if (!self._sortIterator)
|
|
|
|
|
+ return next(null, DocumentSource.EOF); // self is a boost::none in mongo
|
|
|
|
|
+
|
|
|
|
|
+ numAccumulators = self.accumulatorFactories.length; // TODO: whaaat?
|
|
|
|
|
+ for (var i = 0; i < numAccumulators; i++) {
|
|
|
|
|
+ self.currentAccumulators[i].reset(); // prep accumulatorts for new group
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //Note: Skipped the spilled logic
|
|
|
|
|
|
|
+ self._currentId = self.firstPartOfNextGroup.first;
|
|
|
|
|
+ while (self._currentId === _firstPartOfNextGroup.first) {
|
|
|
|
|
+ // Inside of self loop, _firstPartOfNextGroup is the current data being processed.
|
|
|
|
|
+ // At loop exit, it is the first value to be processed in the next group.
|
|
|
|
|
+
|
|
|
|
|
+ switch (numAccumulators) { // mirrors switch in spill()
|
|
|
|
|
+ case 0: // No Accumulators so no Values
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 1: // single accumulators serialize as a single value
|
|
|
|
|
+ self.currentAccumulators[0].process(_firstPartOfNextGroup.second, /*merging=*/true);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ var accumulatorStates = _firstPartOfNextGroup.second.getArray();
|
|
|
|
|
+ for (var j = 0; j < numAccumulators; j++) {
|
|
|
|
|
+ self.currentAccumulators[j].process(accumulatorStates[j], /*merging=*/true);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
|
|
- return next(null, DocumentSource.EOF);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!self._sorterIterator.more()) {
|
|
|
|
|
+ dispose(); // what?
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var id = self.groupsKeys[self.currentGroupsKeysIndex],
|
|
|
|
|
- accumulators = self.groups[id],
|
|
|
|
|
- out = self.makeDocument(id, accumulators /*,mergeableOutput*/);
|
|
|
|
|
|
|
+ _firstPartOfNextGroup = _sorterIterator.next();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if(++self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
|
|
- self.dispose();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return next(null, makeDocument(_currentId, _currentAccumulators, self.expCtx.inShard));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
|
|
+ return next(null, DocumentSource.EOF);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var out = makeDocument(groupsIterator[0], groupsIterator[1], expCtx.inShard);
|
|
|
|
|
+
|
|
|
|
|
+ if (++groupsIterator === groups.end)
|
|
|
|
|
+ dispose();
|
|
|
|
|
+
|
|
|
|
|
+ return next(null, out);
|
|
|
|
|
+
|
|
|
|
|
+ if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
|
|
|
|
|
+ return next(null, DocumentSource.EOF);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var id = self.groupsKeys[self.currentGroupsKeysIndex],
|
|
|
|
|
+ accumulators = self.groups[id],
|
|
|
|
|
+ out = self.makeDocument(id, accumulators, expCtx.inShard);
|
|
|
|
|
+
|
|
|
|
|
+ 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]);
|
|
@@ -198,8 +192,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();
|
|
|
});
|
|
});
|
|
@@ -216,7 +216,10 @@ proto.serialize = function serialize(explain) {
|
|
|
var insides = {};
|
|
var insides = {};
|
|
|
|
|
|
|
|
// add the _id
|
|
// add the _id
|
|
|
- insides._id = this.idExpression.serialize(explain);
|
|
|
|
|
|
|
+ if (this.idFieldNames.length === 0) {
|
|
|
|
|
+ if (this.idExpressions.length !== 1) throw new error("Should only have one _id field");
|
|
|
|
|
+ insides._id = this.idExpressions[0].serialize(explain);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
//add the remaining fields
|
|
//add the remaining fields
|
|
|
var aFacs = this.accumulatorFactories,
|
|
var aFacs = this.accumulatorFactories,
|
|
@@ -256,31 +259,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
|
|
@@ -333,6 +318,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"));
|
|
|
}
|
|
}
|
|
@@ -379,7 +365,7 @@ 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.
|
|
@@ -400,20 +386,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
|
|
|
*
|
|
*
|
|
@@ -425,7 +397,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);
|
|
@@ -456,16 +430,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;
|
|
@@ -476,11 +450,82 @@ proto.makeDocument = function makeDocument(id, accums /*,mergeableOutput*/) {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Sets the id expression for the group
|
|
|
|
|
|
|
+ * Computes the internal representation of the group key.
|
|
|
|
|
+ */
|
|
|
|
|
+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.
|
|
|
|
|
+ */
|
|
|
|
|
+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
|
|
|
|
|
+ vals.forEach(function(val, i) {
|
|
|
|
|
+ doc[self.idFieldNames[i]] = val;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return doc;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+proto.parseIdExpression = function parseIdExpression(groupField, vps) {
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ if (self._getTypeStr(groupField) === 'Object' && groupField !== {}) {
|
|
|
|
|
+ // {_id: {}} is treated as grouping on a constant, not an expression
|
|
|
|
|
+
|
|
|
|
|
+ var idKeyObj = groupField;
|
|
|
|
|
+ if (Object.keys(idKeyObj)[0] == '$') {
|
|
|
|
|
+ self.idExpressions.push(Expression.parseObject(idKeyObj, {}, 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, 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 setIdExpression
|
|
|
|
|
- * @param epxression {Expression} The expression to set
|
|
|
|
|
|
|
+ * @method populate
|
|
|
|
|
+ * @param obj {Object} The object to get the type of
|
|
|
|
|
+ * @return {String} The type of the object as a string
|
|
|
|
|
+ * @async
|
|
|
**/
|
|
**/
|
|
|
-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;
|
|
|
};
|
|
};
|