| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 | 
							- "use strict";
 
- /**
 
-  * Create an empty expression.  Until fields are added, this will evaluateInternal to an empty document (object).
 
-  * @class ObjectExpression
 
-  * @namespace mungedb-aggregate.pipeline.expressions
 
-  * @module mungedb-aggregate
 
-  * @extends mungedb-aggregate.pipeline.expressions.Expression
 
-  * @constructor
 
-  */
 
- var ObjectExpression = module.exports = function ObjectExpression(atRoot) {
 
- 	if (arguments.length !== 1) throw new Error(klass.name + ": expected args: atRoot");
 
- 	this.excludeId = false;
 
- 	this._atRoot = atRoot;
 
- 	this._expressions = {};
 
- 	this._order = [];
 
- }, klass = ObjectExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
- var Document = require("../Document"),
 
- 	Value = require("../Value"),
 
- 	FieldPath = require("../FieldPath"),
 
- 	ConstantExpression = require("./ConstantExpression");
 
- /**
 
-  * Create an empty expression.
 
-  * Until fields are added, this will evaluate to an empty document.
 
-  * @method create
 
-  * @static
 
-  */
 
- klass.create = function create() {
 
- 	return new ObjectExpression(false);
 
- };
 
- /**
 
-  * Like create but uses special handling of _id for root object of $project.
 
-  * @method createRoot
 
-  * @static
 
-  */
 
- klass.createRoot = function createRoot() {
 
- 	return new ObjectExpression(true);
 
- };
 
- proto.optimize = function optimize() {
 
- 	for (var key in this._expressions) { //jshint ignore:line
 
- 		if (!this._expressions.hasOwnProperty(key)) continue;
 
- 		var expr = this._expressions[key];
 
- 		if (expr)
 
- 			this._expressions[key] = expr.optimize();
 
- 	}
 
- 	return this;
 
- };
 
- proto.isSimple = function isSimple() {
 
- 	for (var key in this._expressions) { //jshint ignore:line
 
- 		if (!this._expressions.hasOwnProperty(key)) continue;
 
- 		var expr = this._expressions[key];
 
- 		if (expr && !expr.isSimple())
 
- 			return false;
 
- 	}
 
- 	return true;
 
- };
 
- proto.addDependencies = function addDependencies(deps, path) {
 
- 	var pathStr = "";
 
- 	if (path) {
 
- 		if (path.length === 0) {
 
- 			// we are in the top level of a projection so _id is implicit
 
- 			if (!this.excludeId)
 
- 				deps.fields[Document.ID_PROPERTY_NAME] = 1;
 
- 		} else {
 
- 			var f = new FieldPath(path);
 
- 			pathStr = f.getPath(false);
 
- 			pathStr += ".";
 
- 		}
 
- 	} else {
 
- 		if (this.excludeId) throw new Error("Assertion error");
 
- 	}
 
- 	for (var key in this._expressions) { //jshint ignore:line
 
- 		var expr = this._expressions[key];
 
- 		if (expr instanceof Expression) {
 
- 			if (path) path.push(key);
 
- 			expr.addDependencies(deps, path);
 
- 			if (path) path.pop();
 
- 		} else { // inclusion
 
- 			if (!path) throw new Error("inclusion not supported in objects nested in $expressions; uassert code 16407");
 
- 			deps.fields[pathStr + key] = 1;
 
- 		}
 
- 	}
 
- 	return deps;	// NOTE: added to munge as a convenience
 
- };
 
- /**
 
- * evaluateInternal(), but add the evaluated fields to a given document instead of creating a new one.
 
- * @method addToDocument
 
- * @param pResult the Document to add the evaluated expressions to
 
- * @param currentDoc the input Document for this level
 
- * @param vars the root of the whole input document
 
- */
 
- proto.addToDocument = function addToDocument(out, currentDoc, vars) { //jshint maxcomplexity:22
 
- 	var doneFields = {};	// This is used to mark fields we've done so that we can add the ones we haven't
 
- 	for (var fieldName in currentDoc) { //jshint ignore:line
 
- 		if (!currentDoc.hasOwnProperty(fieldName)) continue;
 
- 		var fieldValue = currentDoc[fieldName];
 
- 		// This field is not supposed to be in the output (unless it is _id)
 
- 		if (!this._expressions.hasOwnProperty(fieldName)) {
 
- 			if (!this.excludeId && this._atRoot && fieldName === Document.ID_PROPERTY_NAME) {
 
- 				// _id from the root doc is always included (until exclusion is supported)
 
- 				// not updating doneFields since "_id" isn't in _expressions
 
- 				out[fieldName] = fieldValue;
 
- 			}
 
- 			continue;
 
- 		}
 
- 		// make sure we don't add this field again
 
- 		doneFields[fieldName] = true;
 
- 		var expr = this._expressions[fieldName];
 
- 		if (!(expr instanceof Expression)) expr = undefined;
 
- 		if (!expr) {
 
- 			// This means pull the matching field from the input document
 
- 			out[fieldName] = fieldValue;
 
- 			continue;
 
- 		}
 
- 		var objExpr = expr instanceof ObjectExpression ? expr : undefined,
 
- 			valueType = Value.getType(fieldValue);
 
- 		if ((valueType !== "Object" && valueType !== "Array") || !objExpr) {
 
- 			// This expression replace the whole field
 
- 			var pValue = expr.evaluateInternal(vars);
 
- 			// don't add field if nothing was found in the subobject
 
- 			if (objExpr && Object.getOwnPropertyNames(pValue).length === 0)
 
- 				continue;
 
- 			/*
 
- 			 * Don't add non-existent values (note:  different from NULL or Undefined);
 
- 			 * this is consistent with existing selection syntax which doesn't
 
- 			 * force the appearance of non-existent fields.
 
- 			 */
 
- 			// if (pValue !== undefined)
 
- 				out[fieldName] = pValue; //NOTE: DEVIATION FROM MONGO: we want to keep these in JS
 
- 			continue;
 
- 		}
 
- 		/*
 
- 		 * Check on the type of the input value.  If it's an
 
- 		 * object, just walk down into that recursively, and
 
- 		 * add it to the result.
 
- 		 */
 
- 		if (valueType === "Object") {
 
- 			var sub = {};
 
- 			objExpr.addToDocument(sub, fieldValue, vars);
 
- 			out[fieldName] = sub;
 
- 		} else if (valueType === "Array") {
 
- 			/*
 
- 			 * If it's an array, we have to do the same thing,
 
- 			 * but to each array element.  Then, add the array
 
- 			 * of results to the current document.
 
- 			 */
 
- 			var result = [],
 
- 				input = fieldValue;
 
- 			for (var fvi = 0, fvl = input.length; fvi < fvl; fvi++) {
 
- 				// can't look for a subfield in a non-object value.
 
- 				if (Value.getType(input[fvi]) !== "Object")
 
- 					continue;
 
- 				var doc = {};
 
- 				objExpr.addToDocument(doc, input[fvi], vars);
 
- 				result.push(doc);
 
- 			}
 
- 			out[fieldName] = result;
 
- 		} else {
 
- 			throw new Error("Assertion failure");
 
- 		}
 
- 	}
 
- 	if (Object.getOwnPropertyNames(doneFields).length === Object.getOwnPropertyNames(this._expressions).length)
 
- 		return out;	//NOTE: munge returns result as a convenience
 
- 	// add any remaining fields we haven't already taken care of
 
- 	for (var i = 0, l = this._order.length; i < l; i++) {
 
- 		var fieldName2 = this._order[i],
 
- 			expr2 = this._expressions[fieldName2];
 
- 		// if we've already dealt with this field, above, do nothing
 
- 		if (doneFields.hasOwnProperty(fieldName2))
 
- 			continue;
 
- 		// this is a missing inclusion field
 
- 		if (expr2 === null || expr2 === undefined)
 
- 			continue;
 
- 		var value = expr2.evaluateInternal(vars);
 
- 		/*
 
- 		 * Don't add non-existent values (note:  different from NULL or Undefined);
 
- 		 * this is consistent with existing selection syntax which doesn't
 
- 		 * force the appearnance of non-existent fields.
 
- 		 */
 
- 		if (value === undefined && !(expr2 instanceof ConstantExpression)) //NOTE: DEVIATION FROM MONGO: only if not {$const:undefined}
 
- 			continue;
 
- 		// don't add field if nothing was found in the subobject
 
- 		if (expr2 instanceof ObjectExpression && Object.getOwnPropertyNames(value).length === 0)
 
- 			continue;
 
- 		out[fieldName2] = value;
 
- 	}
 
- 	return out;	//NOTE: munge returns result as a convenience
 
- };
 
- /**
 
- * estimated number of fields that will be output
 
- * @method getSizeHint
 
- */
 
- proto.getSizeHint = function getSizeHint() {
 
- 	// Note: this can overestimate, but that is better than underestimating
 
- 	return Object.getOwnPropertyNames(this._expressions).length + (this.excludeId ? 0 : 1);
 
- };
 
- /**
 
- * evaluateInternal(), but return a Document instead of a Value-wrapped Document.
 
- * @method evaluateDocument
 
- * @param currentDoc the input Document
 
- * @returns the result document
 
- */
 
- proto.evaluateDocument = function evaluateDocument(vars) {
 
- 	// create and populate the result
 
- 	var out = {};
 
- 	this.addToDocument(out, {}, vars);	// No inclusion field matching.
 
- 	return out;
 
- };
 
- proto.evaluateInternal = function evaluateInternal(vars) {
 
- 	return this.evaluateDocument(vars);
 
- };
 
- /**
 
-  * Add a field to the document expression.
 
-  * @method addField
 
-  * @param fieldPath the path the evaluated expression will have in the result Document
 
-  * @param pExpression the expression to evaluateInternal obtain this field's Value in the result Document
 
-  */
 
- proto.addField = function addField(fieldPath, pExpression) {
 
- 	if (!(fieldPath instanceof FieldPath)) fieldPath = new FieldPath(fieldPath);
 
- 	var fieldPart = fieldPath.getFieldName(0),
 
- 		haveExpr = this._expressions.hasOwnProperty(fieldPart),
 
- 		expr = this._expressions[fieldPart],
 
- 		subObj = expr instanceof ObjectExpression ? expr : undefined;	// inserts if !haveExpr
 
- 	if (!haveExpr) {
 
- 		this._order.push(fieldPart);
 
- 	} else { // we already have an expression or inclusion for this field
 
- 		if (fieldPath.getPathLength() === 1) {
 
- 			// This expression is for right here
 
- 			var newSubObj = pExpression instanceof ObjectExpression ? pExpression : undefined;
 
- 			if (!(subObj && newSubObj))
 
- 				throw new Error("can't add an expression for field " + fieldPart +
 
- 					" because there is already an expression for that field" +
 
- 					" or one of its sub-fields; uassert code 16400"); // we can merge them
 
- 			// Copy everything from the newSubObj to the existing subObj
 
- 			// This is for cases like { $project:{ 'b.c':1, b:{ a:1 } } }
 
- 			for (var i = 0, l = newSubObj._order.length; i < l; ++i) {
 
- 				var key = newSubObj._order[i];
 
- 				// asserts if any fields are dupes
 
- 				subObj.addField(key, newSubObj._expressions[key]);
 
- 			}
 
- 			return;
 
- 		} else {
 
- 			// This expression is for a subfield
 
- 			if (!subObj)
 
- 				throw new Error("can't add an expression for a subfield of " + fieldPart +
 
- 					" because there is already an expression that applies to" +
 
- 					" the whole field; uassert code 16401");
 
- 		}
 
- 	}
 
- 	if (fieldPath.getPathLength() === 1) {
 
- 		if (haveExpr) throw new Error("Assertion error."); // haveExpr case handled above.
 
- 		this._expressions[fieldPart] = pExpression;
 
- 		return;
 
- 	}
 
- 	if (!haveExpr)
 
- 		this._expressions[fieldPart] = subObj = ObjectExpression.create();
 
- 	subObj.addField(fieldPath.tail(), pExpression);
 
- };
 
- /**
 
-  * Add a field path to the set of those to be included.
 
-  *
 
-  * Note that including a nested field implies including everything on the path leading down to it.
 
-  *
 
-  * @method includePath
 
-  * @param fieldPath the name of the field to be included
 
-  */
 
- proto.includePath = function includePath(theFieldPath) {
 
- 	this.addField(theFieldPath, null);
 
- };
 
- proto.serialize = function serialize(explain) {
 
- 	var valBuilder = {};
 
- 	if (this.excludeId)
 
- 		valBuilder[Document.ID_PROPERTY_NAME] = false;
 
- 	for (var i = 0, l = this._order.length; i < l; ++i) {
 
- 		var fieldName = this._order[i];
 
- 		if (!this._expressions.hasOwnProperty(fieldName)) throw new Error("Assertion failure");
 
- 		var expr = this._expressions[fieldName];
 
- 		if (!expr) {
 
- 			valBuilder[fieldName] = true;
 
- 		} else {
 
- 			valBuilder[fieldName] = expr.serialize(explain);
 
- 		}
 
- 	}
 
- 	return valBuilder;
 
- };
 
- /**
 
-  * Get a count of the added fields.
 
-  * @method getFieldCount
 
-  * @returns how many fields have been added
 
-  */
 
- proto.getFieldCount = function getFieldCount() {
 
- 	return Object.getOwnPropertyNames(this._expressions).length;
 
- };
 
 
  |