Просмотр исходного кода

REFS DEVOPS-263 Added new class for intersections. Also included are small minor fixes to other classes

David Aebersold 12 лет назад
Родитель
Сommit
653c3766d7

+ 32 - 26
lib/pipeline/expressions/ModExpression.js

@@ -1,48 +1,54 @@
 "use strict";
 
 /** 
- * An $mod pipeline expression. 
- * @see evaluate 
+ * An $mod pipeline expression.
+ * @see evaluate
  * @class ModExpression
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
  **/
-var ModExpression = module.exports = function ModExpression(){
-	this.fixedAirty(2);
-	if (arguments.length !== 0) throw new Error("zero args expected");
-	base.call(this);
-}, klass = ModExpression, base = require("./NaryExpression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var ModExpression = module.exports = function ModExpression() {
+		this.fixedArity(2);
+		if (arguments.length !== 0) throw new Error("zero args expected");
+		base.call(this);
+}, klass = ModExpression,
+		base = require("./NaryExpression"),
+		proto = klass.prototype = Object.create(base.prototype, {
+				constructor: {
+						value: klass
+				}
+		});
 
 // DEPENDENCIES
 var Value = require("../Value"),
 		Expression = require("./Expression");
 
 // PROTOTYPE MEMBERS
-proto.getOpName = function getOpName(){
-	return "$mod";
+proto.getOpName = function getOpName() {
+		return "$mod";
 };
 
 /** 
- * Takes an array that contains a pair of numbers and returns the remainder of the first number divided by the second number. 
+ * Takes an array that contains a pair of numbers and returns the remainder of the first number divided by the second number.
  * @method evaluate
  **/
-proto.evaluateInternal = function evaluateInternal(doc){
-	this.checkArgCount(2);
-	var left = this.operands[0].evaluateInternal(doc),
-		right = this.operands[1].evaluateInternal(doc);
-	if(left instanceof Date || right instanceof Date) throw new Error("$mod does not support dates; code 16374");
-
-	// pass along jstNULLs and Undefineds
-	if(left === undefined || left === null) return left;
-	if(right === undefined || right === null) return right;
-
-	// ensure we aren't modding by 0
-	right = Value.coerceToDouble(right);
-	if(right === 0) return undefined;
-
-	left = Value.coerceToDouble(left);
-	return left % right;
+proto.evaluateInternal = function evaluateInternal(doc) {
+		this.checkArgCount(2);
+		var left = this.operands[0].evaluateInternal(doc),
+				right = this.operands[1].evaluateInternal(doc);
+		if (left instanceof Date || right instanceof Date) throw new Error("$mod does not support dates; code 16374");
+
+		// pass along jstNULLs and Undefineds
+		if (left === undefined || left === null) return left;
+		if (right === undefined || right === null) return right;
+
+		// ensure we aren't modding by 0
+		right = Value.coerceToDouble(right);
+		if (right === 0) return undefined;
+
+		left = Value.coerceToDouble(left);
+		return left % right;
 };
 
 /** Register Expression */

+ 19 - 13
lib/pipeline/expressions/NotExpression.js

@@ -1,35 +1,41 @@
 "use strict";
 
 /** 
- * A $not pipeline expression. 
- * @see evaluateInternal 
+ * A $not pipeline expression.
+ * @see evaluateInternal
  * @class NotExpression
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
  **/
-var NotExpression = module.exports = function NotExpression(){
-	this.fixedAirty(1);
-	if (arguments.length !== 0) throw new Error("zero args expected");
-	base.call(this);
-}, klass = NotExpression, base = require("./NaryExpression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var NotExpression = module.exports = function NotExpression() {
+		this.fixedArity(1);
+		if (arguments.length !== 0) throw new Error("zero args expected");
+		base.call(this);
+}, klass = NotExpression,
+		base = require("./NaryExpression"),
+		proto = klass.prototype = Object.create(base.prototype, {
+				constructor: {
+						value: klass
+				}
+		});
 
 // DEPENDENCIES
 var Value = require("../Value"),
 		Expression = require("./Expression");
 
 // PROTOTYPE MEMBERS
-proto.getOpName = function getOpName(){
-	return "$not";
+proto.getOpName = function getOpName() {
+		return "$not";
 };
 
 /** 
- * Returns the boolean opposite value passed to it. When passed a true value, $not returns false; when passed a false value, $not returns true. 
+ * Returns the boolean opposite value passed to it. When passed a true value, $not returns false; when passed a false value, $not returns true.
  * @method evaluateInternal
  **/
-proto.evaluateInternal = function evaluateInternal(doc){
-	var op = this.operands[0].evaluateInternal(doc);
-	return !Value.coerceToBool(op);
+proto.evaluateInternal = function evaluateInternal(doc) {
+		var op = this.operands[0].evaluateInternal(doc);
+		return !Value.coerceToBool(op);
 };
 
 /** Register Expression */

+ 174 - 167
lib/pipeline/expressions/ObjectExpression.js

@@ -8,18 +8,25 @@
  * @extends mungedb-aggregate.pipeline.expressions.Expression
  * @constructor
  **/
-var ObjectExpression = module.exports = function ObjectExpression(atRoot){
-	this.fixedAirty(1);
-	if (arguments.length !== 1) throw new Error("one arg expected");
-	this.excludeId = false;	/// <Boolean> for if _id is to be excluded
-	this.atRoot = atRoot;
-	this._expressions = {};	/// <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document
-	this._order = []; /// <Array<String>> this is used to maintain order for generated fields not in the source document
-}, klass = ObjectExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var ObjectExpression = module.exports = function ObjectExpression(atRoot) {
+		this.fixedArity(1);
+		if (arguments.length !== 1) throw new Error("one arg expected");
+		this.excludeId = false; /// <Boolean> for if _id is to be excluded
+		this.atRoot = atRoot;
+		this._expressions = {}; /// <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document
+		this._order = []; /// <Array<String>> this is used to maintain order for generated fields not in the source document
+}, klass = ObjectExpression,
+		Expression = require("./Expression"),
+		base = Expression,
+		proto = klass.prototype = Object.create(base.prototype, {
+				constructor: {
+						value: klass
+				}
+		});
 
 // DEPENDENCIES
 var Document = require("../Document"),
-	FieldPath = require("../FieldPath");
+		FieldPath = require("../FieldPath");
 
 // INSTANCE VARIABLES
 /**
@@ -49,60 +56,60 @@ proto._order = [];
  * @returns the result document
  **/
 proto.evaluateDocument = function evaluateDocument(vars) {
-	// create and populate the result
-	var pResult = {};
-	this.addToDocument(pResult, pResult, vars); // No inclusion field matching.
-	return pResult;
+		// create and populate the result
+		var pResult = {};
+		this.addToDocument(pResult, pResult, vars); // No inclusion field matching.
+		return pResult;
 };
 
 proto.evaluateInternal = function evaluateInternal(vars) { //TODO: collapse with #evaluateDocument()?
-	return this.evaluateDocument(vars);
+		return this.evaluateDocument(vars);
 };
 
-proto.optimize = function optimize(){
-	for (var key in this._expressions) {
-		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null) this._expressions[key] = expr.optimize();
-	}
-	return this;
+proto.optimize = function optimize() {
+		for (var key in this._expressions) {
+				var expr = this._expressions[key];
+				if (expr !== undefined && expr !== null) this._expressions[key] = expr.optimize();
+		}
+		return this;
 };
 
-proto.getIsSimple = function getIsSimple(){
-	for (var key in this._expressions) {
-		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null && !expr.getIsSimple()) return false;
-	}
-	return true;
+proto.getIsSimple = function getIsSimple() {
+		for (var key in this._expressions) {
+				var expr = this._expressions[key];
+				if (expr !== undefined && expr !== null && !expr.getIsSimple()) return false;
+		}
+		return true;
 };
 
-proto.addDependencies = function addDependencies(deps, path){
-	var pathStr = "";
-	if (path instanceof Array) {
-		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;
+proto.addDependencies = function addDependencies(deps, path) {
+		var pathStr = "";
+		if (path instanceof Array) {
+				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 {
+						pathStr = new FieldPath(path).getPath() + ".";
+				}
 		} else {
-			pathStr = new FieldPath(path).getPath() + ".";
+				if (this.excludeId) throw new Error("excludeId is true!");
+		}
+		for (var key in this._expressions) {
+				var expr = this._expressions[key];
+				if (expr !== undefined && expr !== null) {
+						if (path instanceof Array) path.push(key);
+						expr.addDependencies(deps.fields, path);
+						if (path instanceof Array) path.pop();
+				} else { // inclusion
+						if (path === undefined || path === null) throw new Error("inclusion not supported in objects nested in $expressions; uassert code 16407");
+						deps.fields[pathStr + key] = 1;
+				}
 		}
-	} else {
-		if (this.excludeId) throw new Error("excludeId is true!");
-	}
-	for (var key in this._expressions) {
-		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null) {
-			if (path instanceof Array) path.push(key);
-			expr.addDependencies(deps.fields, path);
-			if (path instanceof Array) path.pop();
-		} else { // inclusion
-			if (path === undefined || path === null) throw new Error("inclusion not supported in objects nested in $expressions; uassert code 16407");
-			deps.fields[pathStr + key] = 1;
+		//Array.prototype.push.apply(deps, Object.getOwnPropertyNames(depsSet));
+		for (key in deps.fields) {
+				deps[key] = 1;
 		}
-	}
-	//Array.prototype.push.apply(deps, Object.getOwnPropertyNames(depsSet));
-	for(key in deps.fields) {
-		deps[key] = 1;
-	}
-	return deps;	// NOTE: added to munge as a convenience
+		return deps; // NOTE: added to munge as a convenience
 };
 
 /**
@@ -112,99 +119,99 @@ proto.addDependencies = function addDependencies(deps, path){
  * @param currentDoc the input Document for this level
  * @param vars the root of the whole input document
  **/
-proto.addToDocument = function addToDocument(pResult, currentDoc, vars){
+proto.addToDocument = function addToDocument(pResult, currentDoc, vars) {
 
 
-	var doneFields = {};	// This is used to mark fields we've done so that we can add the ones we haven't
+		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){
-		if (!currentDoc.hasOwnProperty(fieldName)) continue;
-		var fieldValue = currentDoc[fieldName];
+		for (var fieldName in currentDoc) {
+				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
-				pResult[fieldName] = fieldValue;
-			}
-			continue;
-		}
+				// 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
+								pResult[fieldName] = fieldValue;
+						}
+						continue;
+				}
 
-		// make sure we don't add this field again
-		doneFields[fieldName] = true;
+				// make sure we don't add this field again
+				doneFields[fieldName] = true;
 
-		// This means pull the matching field from the input document
-		var expr = this._expressions[fieldName];
-		if (!(expr instanceof Expression)) {
-			pResult[fieldName] = fieldValue;
-			continue;
-		}
+				// This means pull the matching field from the input document
+				var expr = this._expressions[fieldName];
+				if (!(expr instanceof Expression)) {
+						pResult[fieldName] = fieldValue;
+						continue;
+				}
 
-		// Check if this expression replaces the whole field
-		if (!(fieldValue instanceof Object) || (fieldValue.constructor !== Object && fieldValue.constructor !== Array) || !(expr instanceof ObjectExpression)) {
-			var pValue = expr.evaluateInternal(vars);
+				// Check if this expression replaces the whole field
+				if (!(fieldValue instanceof Object) || (fieldValue.constructor !== Object && fieldValue.constructor !== Array) || !(expr instanceof ObjectExpression)) {
+						var pValue = expr.evaluateInternal(vars);
 
-			// don't add field if nothing was found in the subobject
-			if (expr instanceof ObjectExpression && pValue instanceof Object && Object.getOwnPropertyNames(pValue).length === 0) continue;
+						// don't add field if nothing was found in the subobject
+						if (expr instanceof ObjectExpression && pValue instanceof Object && Object.getOwnPropertyNames(pValue).length === 0) continue;
 
-			// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
-			// TODO make missing distinct from Undefined
-			if (pValue !== undefined) pResult[fieldName] = pValue;
-			continue;
-		}
+						// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
+						// TODO make missing distinct from Undefined
+						if (pValue !== undefined) pResult[fieldName] = pValue;
+						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 (fieldValue instanceof Object && fieldValue.constructor === Object) {
-			pResult[fieldName] = expr.addToDocument({}, fieldValue, vars);	//TODO: pretty sure this is broken;
-		} else if (fieldValue instanceof Object && fieldValue.constructor === 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 = [];
-			for(var fvi = 0, fvl = fieldValue.length; fvi < fvl; fvi++){
-				var subValue = fieldValue[fvi];
-				if (subValue.constructor !== Object) continue;	// can't look for a subfield in a non-object value.
-				result.push(expr.addToDocument({}, subValue, vars));
-			}
-			pResult[fieldName] = result;
-		} else {
-			throw new Error("should never happen");	//verify( false );
+				// 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 (fieldValue instanceof Object && fieldValue.constructor === Object) {
+						pResult[fieldName] = expr.addToDocument({}, fieldValue, vars); //TODO: pretty sure this is broken;
+				} else if (fieldValue instanceof Object && fieldValue.constructor === 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 = [];
+						for (var fvi = 0, fvl = fieldValue.length; fvi < fvl; fvi++) {
+								var subValue = fieldValue[fvi];
+								if (subValue.constructor !== Object) continue; // can't look for a subfield in a non-object value.
+								result.push(expr.addToDocument({}, subValue, vars));
+						}
+						pResult[fieldName] = result;
+				} else {
+						throw new Error("should never happen"); //verify( false );
+				}
 		}
-	}
 
-	if (Object.getOwnPropertyNames(doneFields).length == Object.getOwnPropertyNames(this._expressions).length) return pResult;	//NOTE: munge returns result as a convenience
+		if (Object.getOwnPropertyNames(doneFields).length == Object.getOwnPropertyNames(this._expressions).length) return pResult; //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];
-		var expr2 = this._expressions[fieldName2];
+		// 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];
+				var expr2 = this._expressions[fieldName2];
 
-		// if we've already dealt with this field, above, do nothing
-		if (doneFields.hasOwnProperty(fieldName2)) continue;
+				// if we've already dealt with this field, above, do nothing
+				if (doneFields.hasOwnProperty(fieldName2)) continue;
 
-		// this is a missing inclusion field
-		if (!expr2) continue;
+				// this is a missing inclusion field
+				if (!expr2) continue;
 
-		var value = expr2.evaluateInternal(vars);
+				var value = expr2.evaluateInternal(vars);
 
-		// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
-		if (value === undefined) continue;
+				// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
+				if (value === undefined) continue;
 
-		// don't add field if nothing was found in the subobject
-		if (expr2 instanceof ObjectExpression && value && value instanceof Object && Object.getOwnPropertyNames(value) == {} ) continue;
+				// don't add field if nothing was found in the subobject
+				if (expr2 instanceof ObjectExpression && value && value instanceof Object && Object.getOwnPropertyNames(value) == {}) continue;
 
-		pResult[fieldName2] = value;
-	}
+				pResult[fieldName2] = value;
+		}
 
-	return pResult;	//NOTE: munge returns result as a convenience
+		return pResult; //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);
+proto.getSizeHint = function getSizeHint() {
+		// Note: this can overestimate, but that is better than underestimating
+		return Object.getOwnPropertyNames(this._expressions).length + (this.excludeId ? 0 : 1);
 };
 
 /**
@@ -213,42 +220,42 @@ proto.getSizeHint = function getSizeHint(){
  * @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.fields[0],
-		haveExpr = this._expressions.hasOwnProperty(fieldPart),
-		subObj = this._expressions[fieldPart];	// inserts if !haveExpr //NOTE: not in munge & JS it doesn't, handled manually below
-
-	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
-			if (!(subObj instanceof ObjectExpression && typeof pExpression == "object" && pExpression instanceof ObjectExpression)){
-				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 key in pExpression._expressions) {
-				if (pExpression._expressions.hasOwnProperty(key)) {
-					subObj.addField(key, pExpression._expressions[key]); // asserts if any fields are dupes
+proto.addField = function addField(fieldPath, pExpression) {
+		if (!(fieldPath instanceof FieldPath)) fieldPath = new FieldPath(fieldPath);
+		var fieldPart = fieldPath.fields[0],
+				haveExpr = this._expressions.hasOwnProperty(fieldPart),
+				subObj = this._expressions[fieldPart]; // inserts if !haveExpr //NOTE: not in munge & JS it doesn't, handled manually below
+
+		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
+						if (!(subObj instanceof ObjectExpression && typeof pExpression == "object" && pExpression instanceof ObjectExpression)) {
+								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 key in pExpression._expressions) {
+								if (pExpression._expressions.hasOwnProperty(key)) {
+										subObj.addField(key, pExpression._expressions[key]); // asserts if any fields are dupes
+								}
+						}
+						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");
 				}
-			}
-			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("Internal error."); // haveExpr case handled above.
-		this._expressions[fieldPart] = pExpression;
-		return;
-	}
+		if (fieldPath.getPathLength() == 1) {
+				if (haveExpr) throw new Error("Internal error."); // haveExpr case handled above.
+				this._expressions[fieldPart] = pExpression;
+				return;
+		}
 
-	if (!haveExpr) subObj = this._expressions[fieldPart] = new ObjectExpression();
+		if (!haveExpr) subObj = this._expressions[fieldPart] = new ObjectExpression();
 
-	subObj.addField(fieldPath.tail(), pExpression);
+		subObj.addField(fieldPath.tail(), pExpression);
 };
 
 /**
@@ -259,8 +266,8 @@ proto.addField = function addField(fieldPath, pExpression){
  * @method includePath
  * @param fieldPath the name of the field to be included
  **/
-proto.includePath = function includePath(path){
-	this.addField(path, undefined);
+proto.includePath = function includePath(path) {
+		this.addField(path, undefined);
 };
 
 /**
@@ -268,8 +275,8 @@ proto.includePath = function includePath(path){
  * @method getFieldCount
  * @returns how many fields have been added
  **/
-proto.getFieldCount = function getFieldCount(){
-	return Object.getOwnPropertyNames(this._expressions).length;
+proto.getFieldCount = function getFieldCount() {
+		return Object.getOwnPropertyNames(this._expressions).length;
 };
 
 ///**
@@ -284,18 +291,18 @@ proto.getFieldCount = function getFieldCount(){
 //TODO: proto.addToBsonArray = ...?
 
 //NOTE: in `munge` we're not passing the `Object`s in and allowing `toJSON` (was `documentToBson`) to modify it directly and are instead building and returning a new `Object` since that's the way it's actually used
-proto.toJSON = function toJSON(requireExpression){
-	var o = {};
-	if (this.excludeId) o[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("internal error: fieldName from _ordered list not found in _expressions");
-		var fieldValue = this._expressions[fieldName];
-		if (fieldValue === undefined) {
-			o[fieldName] = true; // this is inclusion, not an expression
-		} else {
-			o[fieldName] = fieldValue.toJSON(requireExpression);
+proto.toJSON = function toJSON(requireExpression) {
+		var o = {};
+		if (this.excludeId) o[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("internal error: fieldName from _ordered list not found in _expressions");
+				var fieldValue = this._expressions[fieldName];
+				if (fieldValue === undefined) {
+						o[fieldName] = true; // this is inclusion, not an expression
+				} else {
+						o[fieldName] = fieldValue.toJSON(requireExpression);
+				}
 		}
-	}
-	return o;
-};
+		return o;
+};

+ 48 - 0
lib/pipeline/expressions/SetIntersectionExpression.js

@@ -0,0 +1,48 @@
+"use strict";
+
+/**
+ * A $setintersection pipeline expression.
+ * @see evaluateInternal
+ * @class SetIntersectionExpression
+ * @namespace mungedb-aggregate.pipeline.expressions
+ * @module mungedb-aggregate
+ * @constructor
+ **/
+var SetIntersectionExpression = module.exports = function SetIntersectionExpression() {
+		this.fixedArity(2);
+		if (arguments.length !== 2) throw new Error("two args expected");
+		base.call(this);
+}, klass = SetIntersectionExpression,
+		base = require("./NaryExpression"),
+		proto = klass.prototype = Object.create(base.prototype, {
+				constructor: {
+						value: klass
+				}
+		});
+
+// DEPENDENCIES
+var Value = require("../Value"),
+		Expression = require("./Expression");
+
+// PROTOTYPE MEMBERS
+proto.getOpName = function getOpName() {
+		return "$setintersection";
+};
+
+/**
+ * Takes 2 objects. Returns the intersects of the objects.
+ * @method evaluateInternal
+ **/
+proto.evaluateInternal = function evaluateInternal(vars) {
+		var object1 = this.operands[0].evaluateInternal(vars),
+				object2 = this.operands[1].evaluateInternal(vars);
+		if (object1 instanceof Array) throw new Error(this.getOpName() + ": object 1 must be an object");
+		if (object2 instanceof Array) throw new Error(this.getOpName() + ": object 2 must be an object");
+
+		var result = object1.filter(function(n) {
+		  return object2.indexOf(n) > -1;
+		});
+};
+
+/** Register Expression */
+Expression.registerExpression("$setintersection", SetIntersectionExpression.parse);

+ 2 - 2
lib/pipeline/expressions/SizeExpression.js

@@ -9,7 +9,7 @@
  * @constructor
  **/
 var SizeExpression = module.exports = function SizeExpression() {
-		this.fixedAirty(1);
+		this.fixedArity(1);
 		if (arguments.length !== 1) throw new Error("one arg expected");
 		base.call(this);
 }, klass = SizeExpression,
@@ -35,7 +35,7 @@ proto.getOpName = function getOpName() {
 proto.evaluateInternal = function evaluateInternal(doc) {
 		this.checkArgCount(1);
 		var array = this.operands[0].evaluateInternal(doc);
-		if (array instanceof Date ) throw new Error("$size does not support dates; code 16376");
+		if (array instanceof Date) throw new Error("$size does not support dates; code 16376");
 		return array.length;
 };
 

+ 25 - 19
lib/pipeline/expressions/StrcasecmpExpression.js

@@ -2,40 +2,46 @@
 
 /** 
  * A $strcasecmp pipeline expression.
- * @see evaluate 
+ * @see evaluate
  * @class StrcasecmpExpression
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
  **/
-var StrcasecmpExpression = module.exports = function StrcasecmpExpression(){
-	this.fixedAirty(2);
-	if (arguments.length !== 0) throw new Error("zero args expected");
-	base.call(this);
-}, klass = StrcasecmpExpression, base = require("./NaryExpression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var StrcasecmpExpression = module.exports = function StrcasecmpExpression() {
+		this.fixedArity(2);
+		if (arguments.length !== 0) throw new Error("zero args expected");
+		base.call(this);
+}, klass = StrcasecmpExpression,
+		base = require("./NaryExpression"),
+		proto = klass.prototype = Object.create(base.prototype, {
+				constructor: {
+						value: klass
+				}
+		});
 
 // DEPENDENCIES
 var Value = require("../Value"),
-	NaryExpression = require("./NaryExpression"),
-	Expression = require("./Expression");
+		NaryExpression = require("./NaryExpression"),
+		Expression = require("./Expression");
 
 // PROTOTYPE MEMBERS
-proto.getOpName = function getOpName(){
-	return "$strcasecmp";
+proto.getOpName = function getOpName() {
+		return "$strcasecmp";
 };
 
 /** 
- * Takes in two strings. Returns a number. $strcasecmp is positive if the first string is “greater than” the second and negative if the first string is “less than” the second. $strcasecmp returns 0 if the strings are identical. 
+ * Takes in two strings. Returns a number. $strcasecmp is positive if the first string is “greater than” the second and negative if the first string is “less than” the second. $strcasecmp returns 0 if the strings are identical.
  * @method evaluate
  **/
-proto.evaluateInternal = function evaluateInternal(doc){
-	this.checkArgCount(2);
-	var val1 = this.operands[0].evaluateInternal(doc),
-		val2 = this.operands[1].evaluateInternal(doc),
-		str1 = Value.coerceToString(val1).toUpperCase(),
-		str2 = Value.coerceToString(val2).toUpperCase(),
-		cmp = Value.compare(str1, str2);
-	return cmp;
+proto.evaluateInternal = function evaluateInternal(doc) {
+		this.checkArgCount(2);
+		var val1 = this.operands[0].evaluateInternal(doc),
+				val2 = this.operands[1].evaluateInternal(doc),
+				str1 = Value.coerceToString(val1).toUpperCase(),
+				str2 = Value.coerceToString(val2).toUpperCase(),
+				cmp = Value.compare(str1, str2);
+		return cmp;
 };
 
 /** Register Expression */

+ 1 - 1
lib/pipeline/expressions/SubstrExpression.js

@@ -9,7 +9,7 @@
  * @constructor
  **/
 var SubstrExpression = module.exports = function SubstrExpression() {
-		this.fixedAirty(3);
+		this.fixedArity(3);
 		if (arguments.length !== 0) throw new Error("zero args expected");
 		base.call(this);
 }, klass = SubstrExpression,

+ 1 - 1
lib/pipeline/expressions/SubtractExpression.js

@@ -9,7 +9,7 @@
  * @constructor
  **/
 var SubtractExpression = module.exports = function SubtractExpression() {
-		this.fixedAirty(2);
+		this.fixedArity(2);
 		if (arguments.length !== 0) throw new Error("zero args expected");
 		base.call(this);
 }, klass = SubtractExpression,

+ 1 - 1
lib/pipeline/expressions/ToLowerExpression.js

@@ -9,7 +9,7 @@
  * @constructor
  **/
 var ToLowerExpression = module.exports = function ToLowerExpression() {
-		this.fixedAirty(1);
+		this.fixedArity(1);
 		if (arguments.length !== 0) throw new Error("zero args expected");
 		base.call(this);
 }, klass = ToLowerExpression,

+ 1 - 1
lib/pipeline/expressions/ToUpperExpression.js

@@ -9,7 +9,7 @@
  * @constructor
  **/
 var ToUpperExpression = module.exports = function ToUpperExpression() {
-		this.fixedAirty(1);
+		this.fixedArity(1);
 		if (arguments.length !== 0) throw new Error("zero args expected");
 		base.call(this);
 }, klass = ToUpperExpression,

+ 117 - 0
test/lib/pipeline/expressions/SetIntersectionExpression.js

@@ -0,0 +1,117 @@
+"use strict";
+var assert = require("assert"),
+		SetIntersectionExpression = require("../../../../lib/pipeline/expressions/SetIntersectionExpression"),
+		Expression = require("../../../../lib/pipeline/expressions/Expression");
+
+
+module.exports = {
+
+		"SetIntersectionExpression": {
+
+				"constructor()": {
+
+						"should throw Error when constructing without args": function testConstructor() {
+								assert.throws(function() {
+										new SetIntersectionExpression();
+								});
+						}
+
+				},
+
+				"#getOpName()": {
+
+						"should return the correct op name; $setintersection": function testOpName() {
+								assert.equal(new SetIntersectionExpression([1, 2, 3], [4, 5, 6]).getOpName(), "$setintersection");
+						}
+
+				},
+
+				"#evaluateInternal()": {
+
+						"Should fail if array1 is not an array": function testArg1() {
+								var array1 = "not an array",
+										array2 = [6, 7, 8, 9];
+								assert.throws(function() {
+										Expression.parseOperand({
+												$setintersection: ["$array1", "$array2"]
+										}).evaluateInternal({
+												array1: array1,
+												array2: array2
+										});
+								});
+						},
+
+						"Should fail if array2 is not an array": function testArg2() {
+								var array1 = [1, 2, 3, 4],
+										array2 = "not an array";
+								assert.throws(function() {
+										Expression.parseOperand({
+												$setintersection: ["$array1", "$array2"]
+										}).evaluateInternal({
+												array1: array1,
+												array2: array2
+										});
+								});
+						},
+
+						"Should fail if both are not an array": function testArg1andArg2() {
+								var array1 = "not an array",
+										array2 = "not an array";
+								assert.throws(function() {
+										Expression.parseOperand({
+												$setintersection: ["$array1", "$array2"]
+										}).evaluateInternal({
+												array1: array1,
+												array2: array2
+										});
+								});
+						},
+
+						"Should pass and return an interested set1": function testBasicAssignment() {
+								var array1 = {
+										"a": "3",
+										"c": "4"
+								},
+										array2 = {
+												"a": "3",
+												"b": "3"
+										};
+								assert.strictEqual(Expression.parseOperand({
+										$setintersection: ["$array1", "$array2"]
+								}).evaluateInternal({
+										array1: array1,
+										array2: array2
+								}), {
+										"a": "3"
+								});
+						},
+
+						"Should pass and return an intersected set1": function testBasicAssignment() {
+								var array1 = [1, 2, 3, 4, 5],
+										array2 = [2, 3, 6, 7, 8];
+								assert.strictEqual(Expression.parseOperand({
+										$setintersection: ["$array1", "$array2"]
+								}).evaluateInternal({
+										array1: array1,
+										array2: array2
+								}), [2, 3]);
+						},
+
+						"Should pass and return an intersected set2": function testBasicAssignment() {
+								var array1 = [1, 2, 3, 4, 5],
+										array2 = [7, 8, 9];
+								assert.strictEqual(Expression.parseOperand({
+										$setintersection: ["$array1", "$array2"]
+								}).evaluateInternal({
+										array1: array1,
+										array2: array2
+								}), []);
+						},
+
+				}
+
+		}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);