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

Merge branch 'feature/mongo_2.6.5_expressions' into feature/mongo_2.6.5_documentSource

* feature/mongo_2.6.5_expressions: (24 commits)
  EAGLESIX-2651: fix space indents in tab indented files
  EAGLESIX-2651: update gitignore
  EAGLESIX-2651: rename accumulator tests to _test.js to avoid conflicts with helper files
  EAGLESIX-2651: Map: fix optimize test case typo
  EAGLESIX-2651: rename tests to _test.js to avoid conflicts with helper files
  EAGLESIX-2651: Map: fix optimize test case
  EAGLESIX-2651: FieldRange: remove unused expression
  EAGLESIX-2651: fix jshint in tests
  EAGLESIX-2651: Let: better sync w/ 2.6.5 code, fix tests to match new code
  EAGLESIX-2651: Let: fix jshint and format in test
  EAGLESIX-2651: Nary: fix failing tests, related bugs
  EAGLESIX-2695 it is easier to fix the typos now...
  EAGLESIX-2695 fixed a typo, put a call-out in the test for Kyle's descerning eye.
  EAGLESIX-2695 The Gauntlet, a reasonably thorough test case, works.
  EAGLESIX-1695 in preparation for another merge with expressions
  EAGLESIX-2695 Fiddled with the test cases.
  EAGLESIX-2695 Completed all test cases expect a full blown test that requires expressions that are not yet available.
  EAGLESIX-2695 Corrected a bug in ObjectExpression that would cause optimizations to be dropped on the floor. Continuing to work through $let test cases.  More To Come.
  EAGLESIX-2695 More improvements, with More To Come.
  EAGLESIX-2695 Improving the tests, correct code bugs. More To Come
  ...
Chris Sexton 11 лет назад
Родитель
Сommit
515c398d0a
54 измененных файлов с 464 добавлено и 593 удалено
  1. 17 1
      .gitignore
  2. 3 3
      lib/pipeline/expressions/CompareExpression.js
  3. 8 8
      lib/pipeline/expressions/CondExpression.js
  4. 5 5
      lib/pipeline/expressions/ConstantExpression.js
  5. 2 2
      lib/pipeline/expressions/DayOfMonthExpression.js
  6. 11 11
      lib/pipeline/expressions/DivideExpression.js
  7. 7 13
      lib/pipeline/expressions/Expression.js
  8. 80 80
      lib/pipeline/expressions/FieldPathExpression.js
  9. 0 207
      lib/pipeline/expressions/FieldRangeExpression.js
  10. 75 68
      lib/pipeline/expressions/LetExpression.js
  11. 1 1
      lib/pipeline/expressions/NaryExpression.js
  12. 1 1
      lib/pipeline/expressions/ObjectExpression.js
  13. 0 1
      lib/pipeline/expressions/index.js
  14. 0 0
      test/lib/pipeline/accumulators/AddToSetAccumulator_test.js
  15. 0 0
      test/lib/pipeline/accumulators/AvgAccumulator_test.js
  16. 0 0
      test/lib/pipeline/accumulators/FirstAccumulator_test.js
  17. 0 0
      test/lib/pipeline/accumulators/LastAccumulator_test.js
  18. 0 0
      test/lib/pipeline/accumulators/MinMaxAccumulator_test.js
  19. 0 0
      test/lib/pipeline/accumulators/PushAccumulator_test.js
  20. 0 0
      test/lib/pipeline/accumulators/SumAccumulator_test.js
  21. 3 3
      test/lib/pipeline/expressions/AddExpression_test.js
  22. 2 2
      test/lib/pipeline/expressions/AllElementsTrueExpression_test.js
  23. 0 0
      test/lib/pipeline/expressions/AnyElementTrueExpression_test.js
  24. 1 1
      test/lib/pipeline/expressions/CoerceToBoolExpression_test.js
  25. 1 1
      test/lib/pipeline/expressions/CompareExpression_test.js
  26. 0 0
      test/lib/pipeline/expressions/DayOfMonthExpression_test.js
  27. 0 0
      test/lib/pipeline/expressions/DayOfWeekExpression_test.js
  28. 0 0
      test/lib/pipeline/expressions/DayOfYearExpression_test.js
  29. 0 0
      test/lib/pipeline/expressions/FieldPathExpression_test.js
  30. 0 139
      test/lib/pipeline/expressions/FieldRangeExpression.js
  31. 0 0
      test/lib/pipeline/expressions/HourExpression_test.js
  32. 197 0
      test/lib/pipeline/expressions/LetExpression_test.js
  33. 9 9
      test/lib/pipeline/expressions/MapExpression_test.js
  34. 0 0
      test/lib/pipeline/expressions/MillisecondExpression_test.js
  35. 0 0
      test/lib/pipeline/expressions/MinuteExpression_test.js
  36. 0 0
      test/lib/pipeline/expressions/ModExpression_test.js
  37. 0 0
      test/lib/pipeline/expressions/MonthExpression_test.js
  38. 37 33
      test/lib/pipeline/expressions/NaryExpression_test.js
  39. 0 0
      test/lib/pipeline/expressions/ObjectExpression_test.js
  40. 0 0
      test/lib/pipeline/expressions/SecondExpression_test.js
  41. 0 0
      test/lib/pipeline/expressions/SetDifferenceExpression_test.js
  42. 0 0
      test/lib/pipeline/expressions/SetEqualsExpression_test.js
  43. 0 0
      test/lib/pipeline/expressions/SetIntersectionExpression_test.js
  44. 0 0
      test/lib/pipeline/expressions/SetIsSubsetExpression_test.js
  45. 0 0
      test/lib/pipeline/expressions/SetUnionExpression_test.js
  46. 0 0
      test/lib/pipeline/expressions/SizeExpression_test.js
  47. 0 0
      test/lib/pipeline/expressions/StrcasecmpExpression_test.js
  48. 0 0
      test/lib/pipeline/expressions/SubtractExpression_test.js
  49. 0 0
      test/lib/pipeline/expressions/VariablesIdGenerator_test.js
  50. 0 0
      test/lib/pipeline/expressions/VariablesParseState_test.js
  51. 0 0
      test/lib/pipeline/expressions/Variables_test.js
  52. 0 0
      test/lib/pipeline/expressions/WeekExpression_test.js
  53. 0 0
      test/lib/pipeline/expressions/YearExpression_test.js
  54. 4 4
      test/lib/pipeline/expressions/utils.js

+ 17 - 1
.gitignore

@@ -1,2 +1,18 @@
-/node_modules/
+!.gitkeep
+
+# node
+/node_modules
+npm-debug.log
+
+# IDE files
+/.idea
+/.settings.xml
+/.settings
+/.c9revisions/
+
+# misc files
+*.swp
+.DS_Store
+
+# build files
 /reports/
 /reports/

+ 3 - 3
lib/pipeline/expressions/CompareExpression.js

@@ -9,8 +9,8 @@
  */
  */
 var CompareExpression = module.exports = function CompareExpression(cmpOp) {
 var CompareExpression = module.exports = function CompareExpression(cmpOp) {
 	if (!(arguments.length === 1 && typeof cmpOp === "string")) throw new Error(klass.name + ": args expected: cmpOp");
 	if (!(arguments.length === 1 && typeof cmpOp === "string")) throw new Error(klass.name + ": args expected: cmpOp");
-    this.cmpOp = cmpOp;
-    base.call(this);
+	this.cmpOp = cmpOp;
+	base.call(this);
 }, klass = CompareExpression, base = require("./FixedArityExpressionT")(CompareExpression, 2), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = CompareExpression, base = require("./FixedArityExpressionT")(CompareExpression, 2), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
 
 
@@ -82,7 +82,7 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 		right = this.operands[1].evaluateInternal(vars),
 		right = this.operands[1].evaluateInternal(vars),
 		cmp = Value.compare(left, right);
 		cmp = Value.compare(left, right);
 
 
-    // Make cmp one of 1, 0, or -1.
+	// Make cmp one of 1, 0, or -1.
 	if (cmp === 0) {
 	if (cmp === 0) {
 		//leave as 0
 		//leave as 0
 	} else if (cmp < 0) {
 	} else if (cmp < 0) {

+ 8 - 8
lib/pipeline/expressions/CondExpression.js

@@ -9,11 +9,11 @@
  */
  */
 var CondExpression = module.exports = function CondExpression() {
 var CondExpression = module.exports = function CondExpression() {
 	if (arguments.length !== 0) throw new Error(klass.name + ": expected args: NONE");
 	if (arguments.length !== 0) throw new Error(klass.name + ": expected args: NONE");
-    base.call(this);
+	base.call(this);
 }, klass = CondExpression, base = require("./FixedArityExpressionT")(CondExpression, 3), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = CondExpression, base = require("./FixedArityExpressionT")(CondExpression, 3), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
 var Value = require("../Value"),
 var Value = require("../Value"),
-    Expression = require("./Expression");
+	Expression = require("./Expression");
 
 
 proto.evaluateInternal = function evaluateInternal(vars) {
 proto.evaluateInternal = function evaluateInternal(vars) {
 	var cond = this.operands[0].evaluateInternal(vars);
 	var cond = this.operands[0].evaluateInternal(vars);
@@ -22,12 +22,12 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 };
 };
 
 
 klass.parse = function parse(expr, vps) {
 klass.parse = function parse(expr, vps) {
-    if (Value.getType(expr) !== "Object") {
+	if (Value.getType(expr) !== "Object") {
 		return base.parse(expr, vps);
 		return base.parse(expr, vps);
 	}
 	}
 	// verify(str::equals(expr.fieldName(), "$cond")); //NOTE: DEVIATION FROM MONGO: we do not have fieldName any more and not sure this is even possible anyway
 	// verify(str::equals(expr.fieldName(), "$cond")); //NOTE: DEVIATION FROM MONGO: we do not have fieldName any more and not sure this is even possible anyway
 
 
-    var ret = new CondExpression();
+	var ret = new CondExpression();
 	ret.operands.length = 3;
 	ret.operands.length = 3;
 
 
 	var args = expr;
 	var args = expr;
@@ -44,11 +44,11 @@ klass.parse = function parse(expr, vps) {
 		}
 		}
 	}
 	}
 
 
-    if (!ret.operands[0]) throw new Error("Missing 'if' parameter to $cond; uassert code 17080");
-    if (!ret.operands[1]) throw new Error("Missing 'then' parameter to $cond; uassert code 17081");
-    if (!ret.operands[2]) throw new Error("Missing 'else' parameter to $cond; uassert code 17082");
+	if (!ret.operands[0]) throw new Error("Missing 'if' parameter to $cond; uassert code 17080");
+	if (!ret.operands[1]) throw new Error("Missing 'then' parameter to $cond; uassert code 17081");
+	if (!ret.operands[2]) throw new Error("Missing 'else' parameter to $cond; uassert code 17082");
 
 
-    return ret;
+	return ret;
 };
 };
 
 
 Expression.registerExpression("$cond", CondExpression.parse);
 Expression.registerExpression("$cond", CondExpression.parse);

+ 5 - 5
lib/pipeline/expressions/ConstantExpression.js

@@ -8,9 +8,9 @@
  * @constructor
  * @constructor
  */
  */
 var ConstantExpression = module.exports = function ConstantExpression(value){
 var ConstantExpression = module.exports = function ConstantExpression(value){
-    if (arguments.length !== 1) throw new Error(klass.name + ": args expected: value");
-    this.value = value;
-    base.call(this);
+	if (arguments.length !== 1) throw new Error(klass.name + ": args expected: value");
+	this.value = value;
+	base.call(this);
 }, klass = ConstantExpression, base = require("./FixedArityExpressionT")(ConstantExpression, 1), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = ConstantExpression, base = require("./FixedArityExpressionT")(ConstantExpression, 1), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
 var Expression = require("./Expression");
 var Expression = require("./Expression");
@@ -43,7 +43,7 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 
 
 /// Helper function to easily wrap constants with $const.
 /// Helper function to easily wrap constants with $const.
 function serializeConstant(val) {
 function serializeConstant(val) {
-    return {$const: val};
+	return {$const: val};
 }
 }
 
 
 proto.serialize = function serialize(explain) {
 proto.serialize = function serialize(explain) {
@@ -59,5 +59,5 @@ proto.getOpName = function getOpName() {
 };
 };
 
 
 proto.getValue = function getValue() {
 proto.getValue = function getValue() {
-    return this.value;
+	return this.value;
 };
 };

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

@@ -8,8 +8,8 @@
  * @constructor
  * @constructor
  */
  */
 var DayOfMonthExpression = module.exports = function DayOfMonthExpression() {
 var DayOfMonthExpression = module.exports = function DayOfMonthExpression() {
-    if (arguments.length !== 0) throw new Error(klass.name + ": no args expected");
-    base.call(this);
+	if (arguments.length !== 0) throw new Error(klass.name + ": no args expected");
+	base.call(this);
 }, klass = DayOfMonthExpression, base = require("./FixedArityExpressionT")(klass, 1), proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
 }, klass = DayOfMonthExpression, base = require("./FixedArityExpressionT")(klass, 1), proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
 
 
 var Expression = require("./Expression"),
 var Expression = require("./Expression"),

+ 11 - 11
lib/pipeline/expressions/DivideExpression.js

@@ -10,8 +10,8 @@
  * @constructor
  * @constructor
  **/
  **/
 var DivideExpression = module.exports = function DivideExpression(){
 var DivideExpression = module.exports = function DivideExpression(){
-    if (arguments.length !== 0) throw new Error(klass.name + ": no args expected");
-    base.call(this);
+	if (arguments.length !== 0) throw new Error(klass.name + ": no args expected");
+	base.call(this);
 }, klass = DivideExpression, base = require("./FixedArityExpressionT")(DivideExpression, 2), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = DivideExpression, base = require("./FixedArityExpressionT")(DivideExpression, 2), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
 var Value = require("../Value"),
 var Value = require("../Value"),
@@ -26,16 +26,16 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 		rhs = this.operands[1].evaluateInternal(vars);
 		rhs = this.operands[1].evaluateInternal(vars);
 
 
 	if (typeof lhs === "number" && typeof rhs === "number") {
 	if (typeof lhs === "number" && typeof rhs === "number") {
-        var numer = lhs,
-            denom = rhs;
-        if (denom === 0) throw new Error("can't $divide by zero; uassert code 16608");
+		var numer = lhs,
+			denom = rhs;
+		if (denom === 0) throw new Error("can't $divide by zero; uassert code 16608");
 
 
-        return numer / denom;
-    } else if (lhs === undefined || lhs === null || rhs === undefined || rhs === null) {
-        return null;
-    } else{
-        throw new Error("User assertion: 16609: $divide only supports numeric types, not " + Value.getType(lhs) + " and " + Value.getType(rhs));
-    }
+		return numer / denom;
+	} else if (lhs === undefined || lhs === null || rhs === undefined || rhs === null) {
+		return null;
+	} else{
+		throw new Error("User assertion: 16609: $divide only supports numeric types, not " + Value.getType(lhs) + " and " + Value.getType(rhs));
+	}
 };
 };
 
 
 Expression.registerExpression("$divide", base.parse);
 Expression.registerExpression("$divide", base.parse);

+ 7 - 13
lib/pipeline/expressions/Expression.js

@@ -2,12 +2,6 @@
 
 
 /**
 /**
  * A base class for all pipeline expressions; Performs common expressions within an Op.
  * A base class for all pipeline expressions; Performs common expressions within an Op.
- *
- * NOTE: An object expression can take any of the following forms:
- *
- *      f0: {f1: ..., f2: ..., f3: ...}
- *      f0: {$operator:[operand1, operand2, ...]}
- *
  * @class Expression
  * @class Expression
  * @namespace mungedb-aggregate.pipeline.expressions
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @module mungedb-aggregate
@@ -80,12 +74,12 @@ var ObjectCtx = Expression.ObjectCtx = (function() {
  */
  */
 klass.parseObject = function parseObject(obj, ctx, vps) {
 klass.parseObject = function parseObject(obj, ctx, vps) {
 	if (!(ctx instanceof ObjectCtx)) throw new Error("ctx must be ObjectCtx");
 	if (!(ctx instanceof ObjectCtx)) throw new Error("ctx must be ObjectCtx");
-	/*
-	  An object expression can take any of the following forms:
-
-	  f0: {f1: ..., f2: ..., f3: ...}
-	  f0: {$operator:[operand1, operand2, ...]}
-	*/
+	/**
+	 * An object expression can take any of the following forms:
+	 *
+	 * f0: {f1: ..., f2: ..., f3: ...}
+	 * f0: {$operator:[operand1, operand2, ...]}
+	 */
 
 
 	var expression, // the result
 	var expression, // the result
 		expressionObject, // the alt result
 		expressionObject, // the alt result
@@ -223,7 +217,7 @@ klass.parseOperand = function parseOperand(exprElement, vps) {
 	var t = typeof(exprElement);
 	var t = typeof(exprElement);
 	if (t === "string" && exprElement[0] === "$") {
 	if (t === "string" && exprElement[0] === "$") {
 		//if we got here, this is a field path expression
 		//if we got here, this is a field path expression
-	    return FieldPathExpression.parse(exprElement, vps);
+		return FieldPathExpression.parse(exprElement, vps);
 	} else if (t === "object" && exprElement && exprElement.constructor === Object) {
 	} else if (t === "object" && exprElement && exprElement.constructor === Object) {
 		var oCtx = new ObjectCtx({
 		var oCtx = new ObjectCtx({
 			isDocumentOk: true
 			isDocumentOk: true

+ 80 - 80
lib/pipeline/expressions/FieldPathExpression.js

@@ -1,8 +1,8 @@
 "use strict";
 "use strict";
 
 
 var Expression = require("./Expression"),
 var Expression = require("./Expression"),
-    Variables = require("./Variables"),
-    FieldPath = require("../FieldPath");
+	Variables = require("./Variables"),
+	FieldPath = require("../FieldPath");
 
 
 /**
 /**
  * Create a field path expression.
  * Create a field path expression.
@@ -18,9 +18,9 @@ var Expression = require("./Expression"),
  * @param {String} theFieldPath the field path string, without any leading document indicator
  * @param {String} theFieldPath the field path string, without any leading document indicator
  */
  */
 var FieldPathExpression = module.exports = function FieldPathExpression(theFieldPath, variable) {
 var FieldPathExpression = module.exports = function FieldPathExpression(theFieldPath, variable) {
-    if (arguments.length != 2) throw new Error(klass.name + ": expected args: theFieldPath[, variable]");
-    this._fieldPath = new FieldPath(theFieldPath);
-    this._variable = variable;
+	if (arguments.length != 2) throw new Error(klass.name + ": expected args: theFieldPath[, variable]");
+	this._fieldPath = new FieldPath(theFieldPath);
+	this._variable = variable;
 }, klass = FieldPathExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = FieldPathExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
 /**
 /**
@@ -35,9 +35,9 @@ var FieldPathExpression = module.exports = function FieldPathExpression(theField
  * @param fieldPath the field path string, without any leading document
  * @param fieldPath the field path string, without any leading document
  * indicator
  * indicator
  * @returns the newly created field path expression
  * @returns the newly created field path expression
- **/
+ */
 klass.create = function create(fieldPath) {
 klass.create = function create(fieldPath) {
-    return new FieldPathExpression("CURRENT." + fieldPath, Variables.ROOT_ID);
+	return new FieldPathExpression("CURRENT." + fieldPath, Variables.ROOT_ID);
 };
 };
 
 
 // this is the new version that supports every syntax
 // this is the new version that supports every syntax
@@ -48,52 +48,52 @@ klass.create = function create(fieldPath) {
  * @returns a new FieldPathExpression
  * @returns a new FieldPathExpression
  */
  */
 klass.parse = function parse(raw, vps) {
 klass.parse = function parse(raw, vps) {
-    if (raw[0] !== "$") throw new Error("FieldPath: '" + raw + "' doesn't start with a $; uassert code 16873");
-    if (raw.length < 2) throw new Error("'$' by itself is not a valid FieldPath; uassert code 16872"); // need at least "$" and either "$" or a field name
-    if (raw[1] === "$") {
-        var fieldPath = raw.substr(2), // strip off $$
-            dotIndex = fieldPath.indexOf("."),
-            varName = fieldPath.substr(0, dotIndex !== -1 ? dotIndex : fieldPath.length);
-        Variables.uassertValidNameForUserRead(varName);
-        return new FieldPathExpression(fieldPath, vps.getVariable(varName));
-    } else {
-        return new FieldPathExpression("CURRENT." + raw.substr(1), // strip the "$" prefix
-            vps.getVariable("CURRENT"));
-    }
+	if (raw[0] !== "$") throw new Error("FieldPath: '" + raw + "' doesn't start with a $; uassert code 16873");
+	if (raw.length < 2) throw new Error("'$' by itself is not a valid FieldPath; uassert code 16872"); // need at least "$" and either "$" or a field name
+	if (raw[1] === "$") {
+		var fieldPath = raw.substr(2), // strip off $$
+			dotIndex = fieldPath.indexOf("."),
+			varName = fieldPath.substr(0, dotIndex !== -1 ? dotIndex : fieldPath.length);
+		Variables.uassertValidNameForUserRead(varName);
+		return new FieldPathExpression(fieldPath, vps.getVariable(varName));
+	} else {
+		return new FieldPathExpression("CURRENT." + raw.substr(1), // strip the "$" prefix
+			vps.getVariable("CURRENT"));
+	}
 };
 };
 
 
 proto.optimize = function optimize() {
 proto.optimize = function optimize() {
-    // nothing can be done for these
-    return this;
+	// nothing can be done for these
+	return this;
 };
 };
 
 
 proto.addDependencies = function addDependencies(deps) {
 proto.addDependencies = function addDependencies(deps) {
-    if (this._variable === Variables.ROOT_ID) {
-        if (this._fieldPath.fieldNames.length === 1) {
-            deps.needWholeDocument = true; // need full doc if just "$$ROOT"
-        } else {
-            deps.fields[this._fieldPath.tail().getPath(false)] = 1;
-        }
-    }
+	if (this._variable === Variables.ROOT_ID) {
+		if (this._fieldPath.fieldNames.length === 1) {
+			deps.needWholeDocument = true; // need full doc if just "$$ROOT"
+		} else {
+			deps.fields[this._fieldPath.tail().getPath(false)] = 1;
+		}
+	}
 };
 };
 
 
 /**
 /**
  * Helper for evaluatePath to handle Array case
  * Helper for evaluatePath to handle Array case
  */
  */
 proto._evaluatePathArray = function _evaluatePathArray(index, input) {
 proto._evaluatePathArray = function _evaluatePathArray(index, input) {
-    if (!(input instanceof Array)) throw new Error("must be array; dassert");
-
-    // Check for remaining path in each element of array
-    var result = [];
-    for (var i = 0, l = input.length; i < l; i++) {
-        if (!(input[i] instanceof Object))
-            continue;
-
-        var nested = this._evaluatePath(index, input[i]);
-        if (nested !== undefined)
-            result.push(nested);
-    }
-    return result;
+	if (!(input instanceof Array)) throw new Error("must be array; dassert");
+
+	// Check for remaining path in each element of array
+	var result = [];
+	for (var i = 0, l = input.length; i < l; i++) {
+		if (!(input[i] instanceof Object))
+			continue;
+
+		var nested = this._evaluatePath(index, input[i]);
+		if (nested !== undefined)
+			result.push(nested);
+	}
+	return result;
 };
 };
 
 
 /**
 /**
@@ -110,52 +110,52 @@ proto._evaluatePathArray = function _evaluatePathArray(index, input) {
  * @returns the field found; could be an array
  * @returns the field found; could be an array
  */
  */
 proto._evaluatePath = function _evaluatePath(index, input) {
 proto._evaluatePath = function _evaluatePath(index, input) {
-    // Note this function is very hot so it is important that is is well optimized.
-    // In particular, all return paths should support RVO.
-
-    // if we've hit the end of the path, stop
-    if (index == this._fieldPath.fieldNames.length - 1)
-        return input[this._fieldPath.fieldNames[index]];
-
-    // Try to dive deeper
-    var val = input[this._fieldPath.fieldNames[index]];
-    if (val instanceof Object && val.constructor === Object) {
-        return this._evaluatePath(index + 1, val);
-    } else if (val instanceof Array) {
-        return this._evaluatePathArray(index + 1, val);
-    } else {
-        return undefined;
-    }
+	// Note this function is very hot so it is important that is is well optimized.
+	// In particular, all return paths should support RVO.
+
+	// if we've hit the end of the path, stop
+	if (index == this._fieldPath.fieldNames.length - 1)
+		return input[this._fieldPath.fieldNames[index]];
+
+	// Try to dive deeper
+	var val = input[this._fieldPath.fieldNames[index]];
+	if (val instanceof Object && val.constructor === Object) {
+		return this._evaluatePath(index + 1, val);
+	} else if (val instanceof Array) {
+		return this._evaluatePathArray(index + 1, val);
+	} else {
+		return undefined;
+	}
 };
 };
 
 
 proto.evaluateInternal = function evaluateInternal(vars) {
 proto.evaluateInternal = function evaluateInternal(vars) {
-    if (this._fieldPath.fieldNames.length === 1) // get the whole variable
-        return vars.getValue(this._variable);
-
-    if (this._variable === Variables.ROOT_ID) {
-        // ROOT is always a document so use optimized code path
-        return this._evaluatePath(1, vars.getRoot());
-    }
-
-    var val = vars.getValue(this._variable);
-    if (val instanceof Object && val.constructor === Object) {
-        return this._evaluatePath(1, val);
-    } else if(val instanceof Array) {
-        return this._evaluatePathArray(1,val);
-    } else {
-        return undefined;
-    }
+	if (this._fieldPath.fieldNames.length === 1) // get the whole variable
+		return vars.getValue(this._variable);
+
+	if (this._variable === Variables.ROOT_ID) {
+		// ROOT is always a document so use optimized code path
+		return this._evaluatePath(1, vars.getRoot());
+	}
+
+	var val = vars.getValue(this._variable);
+	if (val instanceof Object && val.constructor === Object) {
+		return this._evaluatePath(1, val);
+	} else if(val instanceof Array) {
+		return this._evaluatePathArray(1,val);
+	} else {
+		return undefined;
+	}
 };
 };
 
 
 proto.serialize = function serialize(){
 proto.serialize = function serialize(){
-    if(this._fieldPath.fieldNames[0] === "CURRENT" && this._fieldPath.fieldNames.length > 1) {
-        // use short form for "$$CURRENT.foo" but not just "$$CURRENT"
-        return "$" + this._fieldPath.tail().getPath(false);
-    } else {
-        return "$$" + this._fieldPath.getPath(false);
-    }
+	if(this._fieldPath.fieldNames[0] === "CURRENT" && this._fieldPath.fieldNames.length > 1) {
+		// use short form for "$$CURRENT.foo" but not just "$$CURRENT"
+		return "$" + this._fieldPath.tail().getPath(false);
+	} else {
+		return "$$" + this._fieldPath.getPath(false);
+	}
 };
 };
 
 
 proto.getFieldPath = function getFieldPath(){
 proto.getFieldPath = function getFieldPath(){
-    return this._fieldPath;
+	return this._fieldPath;
 };
 };

+ 0 - 207
lib/pipeline/expressions/FieldRangeExpression.js

@@ -1,207 +0,0 @@
-"use strict";
-
-/**
- * Create a field range expression.
- *
- * Field ranges are meant to match up with classic Matcher semantics, and therefore are conjunctions.
- *
- * For example, these appear in mongo shell predicates in one of these forms:
- *      { a : C } -> (a == C) // degenerate "point" range
- *      { a : { $lt : C } } -> (a < C) // open range
- *      { a : { $gt : C1, $lte : C2 } } -> ((a > C1) && (a <= C2)) // closed
- *
- * When initially created, a field range only includes one end of the range.  Additional points may be added via intersect().
- *
- * Note that NE and CMP are not supported.
- *
- * @class FieldRangeExpression
- * @namespace mungedb-aggregate.pipeline.expressions
- * @module mungedb-aggregate
- * @extends mungedb-aggregate.pipeline.expressions.Expression
- * @constructor
- * @param pathExpr the field path for extracting the field value
- * @param cmpOp the comparison operator
- * @param value the value to compare against
- * @returns the newly created field range expression
- **/
-var FieldRangeExpression = module.exports = function FieldRangeExpression(pathExpr, cmpOp, value){
-	if (arguments.length !== 3) throw new Error("args expected: pathExpr, cmpOp, and value");
-	this.pathExpr = pathExpr;
-	this.range = new Range({cmpOp:cmpOp, value:value});
-}, klass = FieldRangeExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-// DEPENDENCIES
-var Value = require("../Value"),
-	ConstantExpression = require("./ConstantExpression");
-
-// NESTED CLASSES
-var Range = (function(){
-	/**
-	 * create a new Range; opts is either {cmpOp:..., value:...} or {bottom:..., isBottomOpen:..., top:..., isTopOpen:...}
-	 * @private
-	 **/
-	var klass = function Range(opts){
-		this.isBottomOpen = this.isTopOpen = false;
-		this.bottom = this.top = undefined;
-		if(opts.hasOwnProperty("cmpOp") && opts.hasOwnProperty("value")){
-			switch (opts.cmpOp) {
-				case Expression.CmpOp.EQ:
-					this.bottom = this.top = opts.value;
-					break;
-
-				case Expression.CmpOp.GT:
-					this.isBottomOpen = true;
-					/* falls through */
-				case Expression.CmpOp.GTE:
-					this.isTopOpen = true;
-					this.bottom = opts.value;
-					break;
-
-				case Expression.CmpOp.LT:
-					this.isTopOpen = true;
-					/* falls through */
-				case Expression.CmpOp.LTE:
-					this.isBottomOpen = true;
-					this.top = opts.value;
-					break;
-
-				case Expression.CmpOp.NE:
-				case Expression.CmpOp.CMP:
-					throw new Error("CmpOp not allowed: " + opts.cmpOp);
-
-				default:
-					throw new Error("Unexpected CmpOp: " + opts.cmpOp);
-			}
-		}else{
-			this.bottom = opts.bottom;
-			this.isBottomOpen = opts.isBottomOpen;
-			this.top = opts.top;
-			this.isTopOpen = opts.isTopOpen;
-		}
-	}, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-	// PROTOTYPE MEMBERS
-	proto.intersect = function intersect(range){
-		// Find the max of the bottom end of ranges
-		var maxBottom = range.bottom,
-			maxBottomOpen = range.isBottomOpen;
-		if(this.bottom !== undefined){
-			if(range.bottom === undefined){
-				maxBottom = this.bottom;
-				maxBottomOpen = this.isBottomOpen;
-			}else{
-				if(Value.compare(this.bottom, range.bottom) === 0){
-					maxBottomOpen = this.isBottomOpen || range.isBottomOpen;
-				}else{
-					maxBottom = this.bottom;
-					maxBottomOpen = this.isBottomOpen;
-				}
-			}
-		}
-		// Find the min of the tops of the ranges
-		var minTop = range.top,
-			minTopOpen = range.isTopOpen;
-		if(this.top !== undefined){
-			if(range.top === undefined){
-				minTop = this.top;
-				minTopOpen = this.isTopOpen;
-			}else{
-				if(Value.compare(this.top, range.top) === 0){
-					minTopOpen = this.isTopOpen || range.isTopOpen;
-				}else{
-					minTop = this.top;
-					minTopOpen = this.isTopOpen;
-				}
-			}
-		}
-		if(Value.compare(maxBottom, minTop) <= 0)
-			return new Range({bottom:maxBottom, isBottomOpen:maxBottomOpen, top:minTop, isTopOpen:minTopOpen});
-		return null; // empty intersection
-	};
-
-	proto.contains = function contains(value){
-		var cmp;
-		if(this.bottom !== undefined){
-			cmp = Value.compare(value, this.bottom);
-			if(cmp < 0) return false;
-			if(this.isBottomOpen && cmp === 0) return false;
-		}
-		if(this.top !== undefined){
-			cmp = Value.compare(value, this.top);
-			if(cmp > 0) return false;
-			if(this.isTopOpen && cmp === 0) return false;
-		}
-		return true;
-	};
-
-	return klass;
-})();
-
-// PROTOTYPE MEMBERS
-proto.evaluate = function evaluate(obj){
-	if(this.range === undefined) return false;
-	var value = this.pathExpr.evaluate(obj);
-	if(value instanceof Array)
-		throw new Error('FieldRangeExpression cannot evaluate an array.');
-	return this.range.contains(value);
-};
-
-proto.optimize = function optimize(){
-	if(this.range === undefined) return new ConstantExpression(false);
-	if(this.range.bottom === undefined && this.range.top === undefined) return new ConstantExpression(true);
-	return this;
-};
-
-proto.addDependencies = function(deps){
-	return this.pathExpr.addDependencies(deps);
-};
-
-/**
- * Add an intersecting range.
- *
- * This can be done any number of times after creation.  The range is
- * internally optimized for each new addition.  If the new intersection
- * extends or reduces the values within the range, the internal
- * representation is adjusted to reflect that.
- *
- * Note that NE and CMP are not supported.
- *
- * @method intersect
- * @param cmpOp the comparison operator
- * @param pValue the value to compare against
- **/
-proto.intersect = function intersect(cmpOp, value){
-	this.range = this.range.intersect(new Range({cmpOp:cmpOp, value:value}));
-};
-
-proto.toJSON = function toJSON(){
-	if (this.range === undefined) return false; //nothing will satisfy this predicate
-	if (this.range.top === undefined && this.range.bottom === undefined) return true; // any value will satisfy this predicate
-
-	// FIXME Append constant values using the $const operator.  SERVER-6769
-
-	var json = {};
-	if (this.range.top === this.range.bottom) {
-		json[Expression.CmpOp.EQ] = [this.pathExpr.toJSON(), this.range.top];
-	}else{
-		var leftOp = {};
-		if (this.range.bottom !== undefined) {
-			leftOp[this.range.isBottomOpen ? Expression.CmpOp.GT : Expression.CmpOp.GTE] = [this.pathExpr.toJSON(), this.range.bottom];
-			if (this.range.top === undefined) return leftOp;
-		}
-
-		var rightOp = {};
-		if(this.range.top !== undefined){
-			rightOp[this.range.isTopOpen ? Expression.CmpOp.LT : Expression.CmpOp.LTE] = [this.pathExpr.toJSON(), this.range.top];
-			if (this.range.bottom === undefined) return rightOp;
-		}
-
-		json.$and = [leftOp, rightOp];
-	}
-	return json;
-};
-
-//TODO: proto.addToBson = ...?
-//TODO: proto.addToBsonObj = ...?
-//TODO: proto.addToBsonArray = ...?
-//TODO: proto.toMatcherBson = ...? WILL PROBABLY NEED THESE...

+ 75 - 68
lib/pipeline/expressions/LetExpression.js

@@ -1,119 +1,126 @@
 "use strict";
 "use strict";
 
 
 var LetExpression = module.exports = function LetExpression(vars, subExpression){
 var LetExpression = module.exports = function LetExpression(vars, subExpression){
-	//if (arguments.length !== 2) throw new Error("Two args expected");
+	if (arguments.length !== 2) throw new Error(klass.name + ": expected args: vars, subExpression");
 	this._variables = vars;
 	this._variables = vars;
 	this._subExpression = subExpression;
 	this._subExpression = subExpression;
 }, klass = LetExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 }, klass = LetExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-Expression.registerExpression("$let", LetExpression.parse);
 
 
-// DEPENDENCIES
-var Variables = require("./Variables"),
-	VariablesParseState = require("./VariablesParseState");
+var Value = require("../Value"),
+	Variables = require("./Variables");
 
 
-// PROTOTYPE MEMBERS
 
 
+function NameAndExpression(name, expr){
+	this.name = name;
+	this.expression = expr;
+}
 
 
-proto.parse = function parse(expr, vpsIn){
-	if(!("$let" in expr)) {
-		throw new Error("Tried to create a $let with something other than let. Looks like your parse map went all funny.");
-	}
 
 
-	if(typeof(expr.$let) !== 'object' || (expr.$let instanceof Array)) {
-		throw new Error("$let only supports an object as its argument: 16874");
-	}
+klass.parse = function parse(expr, vpsIn){
 
 
-	var args = expr.$let,
-		varsElem = args.vars,
-		inElem = args['in']; // args.in; ??
-
-	//NOTE: DEVIATION FROM MONGO: 1. These if statements are in a loop in the c++ version,
-	// 2. 'vars' and 'in' are each mandatory here. in the c++ code you only need one of the two.
-	// 3. Below, we croak if there are more than 2 arguments.  The original does not have this limitation, specifically.
-	// Upon further review, I think our code is more accurate.  The c++ code will accept if there are multiple 'in'
-	// or 'var' values. The previous ones will be overwritten by newer ones.
-	//
-	// Final note - I think this code is fine.
-	//
-	if(!varsElem) {
-		throw new Error("Missing 'vars' parameter to $let: 16876");
-	}
-	if(!inElem) {
-		throw new Error("Missing 'in' parameter to $let: 16877");
-	}
+	// if (!(exprFieldName === "$let")) throw new Error("Assertion failure"); //NOTE: DEVIATION FROM MONGO: we do not have exprFieldName here
 
 
-	// Should this be !== 2?  Why would we have fewer than 2 arguments?  Why do we even care what the length of the
-	// array is? It may be an optimization of sorts. But what we're really wanting here is, 'If any keys are not "in"
-	// or "vars" then we need to bugcheck.'
-	if(Object.keys(args).length > 2) {
-		var bogus = Object.keys(args).filter(function(x) {return !(x === 'in' || x === 'vars');});
-		throw new Error("Unrecognized parameter to $let: " + bogus.join(",") + "- 16875");
-	}
+	if (Value.getType(expr) !== "Object")
+		throw new Error("$let only supports an object as it's argument; uassert code 16874");
+	var args = expr;
 
 
-	var vpsSub = new VariablesParseState(vpsIn),
-		vars = {};
+	// varsElem must be parsed before inElem regardless of BSON order.
+	var varsElem,
+		inElem;
+	for (var argFieldName in args) {
+		var arg = args[argFieldName];
+		if (argFieldName === "vars") {
+			varsElem = arg;
+		} else if (argFieldName === "in") {
+			inElem = arg;
+		} else {
+			throw new Error("Unrecognized parameter to $let: " + argFieldName + "; uasserted code 16875");
+		}
+	}
 
 
-	for(var varName in varsElem) {
+	if (!varsElem)
+		throw new Error("Missing 'vars' parameter to $let; uassert code 16876");
+	if (!inElem)
+		throw new Error("Missing 'in' parameter to $let; uassert code 16877");
+
+	// parse "vars"
+	var vpsSub = vpsIn, // vpsSub gets our vars, vpsIn doesn't.
+		vars = {}; // using like a VariableMap
+	if (Value.getType(varsElem) !== "Object") //NOTE: emulate varsElem.embeddedObjectUserCheck()
+		throw new Error("invalid parameter: expected an object (vars); uasserted code 10065");
+	for (var varName in varsElem) {
+		var varElem = varsElem[varName];
 		Variables.uassertValidNameForUserWrite(varName);
 		Variables.uassertValidNameForUserWrite(varName);
 		var id = vpsSub.defineVariable(varName);
 		var id = vpsSub.defineVariable(varName);
 
 
-		vars[id] = {};
-		vars[id][varName] = Expression.parseOperand(varsElem, vpsIn);
+		vars[id] = new NameAndExpression(varName,
+			Expression.parseOperand(varElem, vpsIn)); // only has outer vars
 	}
 	}
 
 
-	var subExpression = Expression.parseOperand(inElem, vpsSub);
+	// parse "in"
+	var subExpression = Expression.parseOperand(inElem, vpsSub); // has our vars
+
 	return new LetExpression(vars, subExpression);
 	return new LetExpression(vars, subExpression);
 };
 };
 
 
+
 proto.optimize = function optimize() {
 proto.optimize = function optimize() {
-	if(this._variables.empty()) {
+	if (Object.keys(this._variables).length === 0) {
+		// we aren't binding any variables so just return the subexpression
 		return this._subExpression.optimize();
 		return this._subExpression.optimize();
 	}
 	}
 
 
-	for(var id in this._variables){
-		for(var name in this._variables[id]) {
-			//NOTE: DEVIATION FROM MONGO: This is actually ok. The c++ code does this with a single map. The js structure
-			// is nested objects.
-			this._variables[id][name] = this._variables[id][name].optimize();
-		}
+	for (var id in this._variables) {
+		this._variables[id].expression = this._variables[id].expression.optimize();
 	}
 	}
 
 
+	// TODO be smarter with constant "variables"
 	this._subExpression = this._subExpression.optimize();
 	this._subExpression = this._subExpression.optimize();
 
 
 	return this;
 	return this;
 };
 };
 
 
+
 proto.serialize = function serialize(explain) {
 proto.serialize = function serialize(explain) {
 	var vars = {};
 	var vars = {};
-	for(var id in this._variables) {
-		for(var name in this._variables[id]) {
-			vars[name] = this._variables[id][name];
-		}
+	for (var id in this._variables) {
+		vars[this._variables[id].name] = this._variables[id].expression.serialize(explain);
 	}
 	}
 
 
-	return {$let: {vars:vars, 'in':this._subExpression.serialize(explain)}};
+	return {
+		$let: {
+			vars: vars,
+			in : this._subExpression.serialize(explain)
+		}
+	};
 };
 };
 
 
+
 proto.evaluateInternal = function evaluateInternal(vars) {
 proto.evaluateInternal = function evaluateInternal(vars) {
-	for(var id in this._variables) {
-		for(var name in this._variables[id]) {
-			vars.setValue(id, this._variables[id][name]);
-		}
+	for (var id in this._variables) {
+		var itFirst = +id, //NOTE: using the unary + to coerce it to a Number
+			itSecond = this._variables[itFirst];
+		// It is guaranteed at parse-time that these expressions don't use the variable ids we
+		// are setting
+		vars.setValue(itFirst,
+			itSecond.expression.evaluateInternal(vars));
 	}
 	}
 
 
 	return this._subExpression.evaluateInternal(vars);
 	return this._subExpression.evaluateInternal(vars);
 };
 };
 
 
+
 proto.addDependencies = function addDependencies(deps, path){
 proto.addDependencies = function addDependencies(deps, path){
-	for(var id in this._variables) {
-		for(var name in this._variables[id]) {
-			this._variables[id][name].addDependencies(deps);
-		}
+	for (var id in this._variables) {
+		var itFirst = +id, //NOTE: using the unary + to coerce it to a Number
+			itSecond = this._variables[itFirst];
+			itSecond.expression.addDependencies(deps);
 	}
 	}
-	this._subExpression.addDependencies(deps, path);
-	return deps; //NOTE: DEVIATION FROM MONGO: The c++ version does not return a value. We seem to use the returned value
-					// (or something from a different method named
-					// addDependencies) in many places.
 
 
+	// TODO be smarter when CURRENT is a bound variable
+	this._subExpression.addDependencies(deps);
 };
 };
+
+
+Expression.registerExpression("$let", LetExpression.parse);

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

@@ -62,7 +62,7 @@ proto.optimize = function optimize() {
 			// this is commutative and associative.  We detect sameness of
 			// this is commutative and associative.  We detect sameness of
 			// the child operator by checking for equality of the opNames
 			// the child operator by checking for equality of the opNames
 			var nary = expr instanceof NaryExpression ? expr : undefined;
 			var nary = expr instanceof NaryExpression ? expr : undefined;
-			if (!nary || nary.getOpName() !== this.getOpName) {
+			if (!nary || nary.getOpName() !== this.getOpName()) {
 				nonConstExprs.push(expr);
 				nonConstExprs.push(expr);
 			} else {
 			} else {
 				// same expression, so flatten by adding to vpOperand which
 				// same expression, so flatten by adding to vpOperand which

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

@@ -49,7 +49,7 @@ proto.optimize = function optimize() {
 		if (!this._expressions.hasOwnProperty(key)) continue;
 		if (!this._expressions.hasOwnProperty(key)) continue;
 		var expr = this._expressions[key];
 		var expr = this._expressions[key];
 		if (expr)
 		if (expr)
-			expr.optimize();
+			this._expressions[key] = expr.optimize();
 	}
 	}
 	return this;
 	return this;
 };
 };

+ 0 - 1
lib/pipeline/expressions/index.js

@@ -12,7 +12,6 @@ module.exports = {
 	DivideExpression: require("./DivideExpression.js"),
 	DivideExpression: require("./DivideExpression.js"),
 	Expression: require("./Expression.js"),
 	Expression: require("./Expression.js"),
 	FieldPathExpression: require("./FieldPathExpression.js"),
 	FieldPathExpression: require("./FieldPathExpression.js"),
-	FieldRangeExpression: require("./FieldRangeExpression.js"),
 	HourExpression: require("./HourExpression.js"),
 	HourExpression: require("./HourExpression.js"),
 	IfNullExpression: require("./IfNullExpression.js"),
 	IfNullExpression: require("./IfNullExpression.js"),
 	MinuteExpression: require("./MinuteExpression.js"),
 	MinuteExpression: require("./MinuteExpression.js"),

+ 0 - 0
test/lib/pipeline/accumulators/AddToSetAccumulator.js → test/lib/pipeline/accumulators/AddToSetAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/AvgAccumulator.js → test/lib/pipeline/accumulators/AvgAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/FirstAccumulator.js → test/lib/pipeline/accumulators/FirstAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/LastAccumulator.js → test/lib/pipeline/accumulators/LastAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/MinMaxAccumulator.js → test/lib/pipeline/accumulators/MinMaxAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/PushAccumulator.js → test/lib/pipeline/accumulators/PushAccumulator_test.js


+ 0 - 0
test/lib/pipeline/accumulators/SumAccumulator.js → test/lib/pipeline/accumulators/SumAccumulator_test.js


+ 3 - 3
test/lib/pipeline/expressions/AddExpression_test.js

@@ -49,8 +49,8 @@ var TestBase = function TestBase(overrides) {
 		}, base = ExpectedResultBase, proto = klass.prototype = Object.create(base.prototype);
 		}, base = ExpectedResultBase, proto = klass.prototype = Object.create(base.prototype);
 		proto.run = function() {
 		proto.run = function() {
 			base.prototype.run.call(this);
 			base.prototype.run.call(this);
-            // Now add the operands in the reverse direction.
-            this._reverse = true;
+			// Now add the operands in the reverse direction.
+			this._reverse = true;
 			base.prototype.run.call(this);
 			base.prototype.run.call(this);
 		};
 		};
 		proto.populateOperands = function(expr) {
 		proto.populateOperands = function(expr) {
@@ -122,7 +122,7 @@ exports.AddExpression = {
 		"w/ 1 operand": {
 		"w/ 1 operand": {
 
 
 			"should pass through a single int": function testInt() {
 			"should pass through a single int": function testInt() {
-        		/** Single int argument. */
+				/** Single int argument. */
 				new SingleOperandBase({
 				new SingleOperandBase({
 					operand: 1,
 					operand: 1,
 				}).run();
 				}).run();

+ 2 - 2
test/lib/pipeline/expressions/AllElementsTrueExpression.js → test/lib/pipeline/expressions/AllElementsTrueExpression_test.js

@@ -40,7 +40,7 @@ var ExpectedResultBase = (function() {
 			var asserters = spec.error,
 			var asserters = spec.error,
 				n = asserters.length;
 				n = asserters.length;
 			for (var i = 0; i < n; ++i) {
 			for (var i = 0; i < n; ++i) {
-                // var obj2 = {<asserters[i]>: args}; //NOTE: DEVIATION FROM MONGO: see parseExpression below
+				// var obj2 = {<asserters[i]>: args}; //NOTE: DEVIATION FROM MONGO: see parseExpression below
 				var idGenerator2 = new VariablesIdGenerator(),
 				var idGenerator2 = new VariablesIdGenerator(),
 					vps2 = new VariablesParseState(idGenerator2);
 					vps2 = new VariablesParseState(idGenerator2);
 				assert.throws(function() {
 				assert.throws(function() {
@@ -82,7 +82,7 @@ exports.AllElementsTrueExpression = {
 	"#evaluate()": {
 	"#evaluate()": {
 
 
 		"should return false if just false": function JustFalse() {
 		"should return false if just false": function JustFalse() {
-            new ExpectedResultBase({
+			new ExpectedResultBase({
 				getSpec: {
 				getSpec: {
 					input: [[false]],
 					input: [[false]],
 					expected: {
 					expected: {

+ 0 - 0
test/lib/pipeline/expressions/AnyElementTrueExpression.js → test/lib/pipeline/expressions/AnyElementTrueExpression_test.js


+ 1 - 1
test/lib/pipeline/expressions/CoerceToBoolExpression.js → test/lib/pipeline/expressions/CoerceToBoolExpression_test.js

@@ -64,7 +64,7 @@ exports.CoerceToBoolExpression = {
 		"should be able to output in to JSON Object": function testAddToBsonObj() {
 		"should be able to output in to JSON Object": function testAddToBsonObj() {
 			/** Output to BSONObj. */
 			/** Output to BSONObj. */
 			var expr = CoerceToBoolExpression.create(FieldPathExpression.create("foo"));
 			var expr = CoerceToBoolExpression.create(FieldPathExpression.create("foo"));
-            // serialized as $and because CoerceToBool isn't an ExpressionNary
+			// serialized as $and because CoerceToBool isn't an ExpressionNary
 			assert.deepEqual({field:{$and:["$foo"]}}, {field:expr.serialize(false)});
 			assert.deepEqual({field:{$and:["$foo"]}}, {field:expr.serialize(false)});
 		},
 		},
 
 

+ 1 - 1
test/lib/pipeline/expressions/CompareExpression.js → test/lib/pipeline/expressions/CompareExpression_test.js

@@ -329,7 +329,7 @@ exports.CompareExpression = {
 			}).run();
 			}).run();
 		},
 		},
 
 
-        /** Incompatible types can be compared. */
+		/** Incompatible types can be compared. */
 		"IncompatibleTypes": function IncompatibleTypes() {
 		"IncompatibleTypes": function IncompatibleTypes() {
 			var specElement = {$ne:["a",1]},
 			var specElement = {$ne:["a",1]},
 				idGenerator = new VariablesIdGenerator(),
 				idGenerator = new VariablesIdGenerator(),

+ 0 - 0
test/lib/pipeline/expressions/DayOfMonthExpression.js → test/lib/pipeline/expressions/DayOfMonthExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/DayOfWeekExpression.js → test/lib/pipeline/expressions/DayOfWeekExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/DayOfYearExpression.js → test/lib/pipeline/expressions/DayOfYearExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/FieldPathExpression.js → test/lib/pipeline/expressions/FieldPathExpression_test.js


+ 0 - 139
test/lib/pipeline/expressions/FieldRangeExpression.js

@@ -1,139 +0,0 @@
-"use strict";
-var assert = require("assert"),
-	FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
-	FieldRangeExpression = require("../../../../lib/pipeline/expressions/FieldRangeExpression");
-
-
-module.exports = {
-
-	"FieldRangeExpression": {
-
-		"constructor()": {
-
-			"should throw Error if no args": function testInvalid(){
-				assert.throws(function() {
-					new FieldRangeExpression();
-				});
-			}
-
-		},
-
-		"#evaluate()": {
-
-
-			"$eq": {
-
-				"should return false if documentValue < rangeValue": function testEqLt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$eq", 1).evaluate({a:0}), false);
-				},
-
-				"should return true if documentValue == rangeValue": function testEqEq() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$eq", 1).evaluate({a:1}), true);
-				},
-
-				"should return false if documentValue > rangeValue": function testEqGt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$eq", 1).evaluate({a:2}), false);
-				}
-
-			},
-
-			"$lt": {
-
-				"should return true if documentValue < rangeValue": function testLtLt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lt", "y").evaluate({a:"x"}), true);
-				},
-
-				"should return false if documentValue == rangeValue": function testLtEq() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lt", "y").evaluate({a:"y"}), false);
-				},
-
-				"should return false if documentValue > rangeValue": function testLtGt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lt", "y").evaluate({a:"z"}), false);
-				}
-
-			},
-
-			"$lte": {
-
-				"should return true if documentValue < rangeValue": function testLtLt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lte", 1.1).evaluate({a:1.0}), true);
-				},
-
-				"should return true if documentValue == rangeValue": function testLtEq() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lte", 1.1).evaluate({a:1.1}), true);
-				},
-
-				"should return false if documentValue > rangeValue": function testLtGt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$lte", 1.1).evaluate({a:1.2}), false);
-				}
-
-			},
-
-			"$gt": {
-
-				"should return false if documentValue < rangeValue": function testLtLt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gt", 100).evaluate({a:50}), false);
-				},
-
-				"should return false if documentValue == rangeValue": function testLtEq() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gt", 100).evaluate({a:100}), false);
-				},
-
-				"should return true if documentValue > rangeValue": function testLtGt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gt", 100).evaluate({a:150}), true);
-				}
-
-			},
-
-			"$gte": {
-
-				"should return false if documentValue < rangeValue": function testLtLt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gte", "abc").evaluate({a:"a"}), false);
-				},
-
-				"should return true if documentValue == rangeValue": function testLtEq() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gte", "abc").evaluate({a:"abc"}), true);
-				},
-
-				"should return true if documentValue > rangeValue": function testLtGt() {
-					assert.strictEqual(new FieldRangeExpression(new FieldPathExpression("a"), "$gte", "abc").evaluate({a:"abcd"}), true);
-				}
-
-			},
-
-			"should throw Error if given multikey values": function testMultikey(){
-				assert.throws(function(){
-					new FieldRangeExpression(new FieldPathExpression("a"), "$eq", 0).evaluate({a:[1,0,2]});
-				});
-			}
-
-		},
-
-//		"#optimize()": {
-//			"should optimize if ...": function testOptimize(){
-//			},
-//			"should not optimize if ...": function testNoOptimize(){
-//			}
-//		},
-
-		"#addDependencies()": {
-
-			"should return the range's path as a dependency": function testDependencies(){
-				var deps = new FieldRangeExpression(new FieldPathExpression("a.b.c"), "$eq", 0).addDependencies({});
-				assert.strictEqual(Object.keys(deps).length, 1);
-				assert.ok(deps['a.b.c']);
-			}
-
-		},
-
-//		"#intersect()": {
-//		},
-
-//		"#toJSON()": {
-//		}
-
-	}
-
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 0 - 0
test/lib/pipeline/expressions/HourExpression.js → test/lib/pipeline/expressions/HourExpression_test.js


+ 197 - 0
test/lib/pipeline/expressions/LetExpression_test.js

@@ -0,0 +1,197 @@
+"use strict";
+var assert = require("assert"),
+	DepsTracker = require("../../../../lib/pipeline/DepsTracker"),
+	LetExpression = require("../../../../lib/pipeline/expressions/LetExpression"),
+	ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
+	MultiplyExpression = require("../../../../lib/pipeline/expressions/MultiplyExpression"), //jshint ignore:line
+	AddExpression = require("../../../../lib/pipeline/expressions/AddExpression"), //jshint ignore:line
+	CondExpression = require("../../../../lib/pipeline/expressions/CondExpression"), //jshint ignore:line
+	FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"), //jshint ignore:line
+	VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
+	Variables = require("../../../../lib/pipeline/expressions/Variables"),
+	VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
+	Expression = require("../../../../lib/pipeline/expressions/Expression");
+
+// Mocha one-liner to make these tests self-hosted
+if(!module.parent)return(require.cache[__filename]=null,(new(require("mocha"))({ui:"exports",reporter:"spec",grep:process.env.TEST_GREP})).addFile(__filename).run(process.exit));
+
+exports.LetExpression = {
+
+	beforeEach: function() {
+		this.vps = new VariablesParseState(new VariablesIdGenerator());
+	},
+
+	"constructor()": {
+
+		"should throw an Error when constructing without args": function() {
+			assert.throws(function() {
+				new LetExpression();
+			});
+		},
+
+		"should throw Error when constructing with one arg": function() {
+			assert.throws(function() {
+				new LetExpression(1);
+			});
+		},
+
+		"should not throw when constructing with two args": function() {
+			assert.doesNotThrow(function() {
+				new LetExpression(1, 2);
+			});
+		},
+
+	},
+
+	"#parse()": {
+
+		"should throw if $let isn't in expr": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$xlet: ['$$a', 1]}, self.vps);
+			}, /15999/);
+		},
+
+		"should throw if the $let expression isn't an object": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$let: "this is not an object"}, self.vps);
+			}, /16874/);
+		},
+
+		"should throw if the $let expression is an array": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$let: [1, 2, 3]}, self.vps);
+			}, /16874/);
+		},
+
+		"should throw if there is no vars parameter to $let": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$let: {vars: undefined}}, self.vps);
+			}, /16876/);
+		},
+
+		"should throw if there is no input parameter to $let": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$let: {vars: 1, in: undefined}}, self.vps);
+			}, /16877/);
+		},
+
+		"should throw if any of the arguments to $let are not 'in' or 'vars'": function() {
+			var self = this;
+			assert.throws(function() {
+				Expression.parseOperand({$let: {vars: 1, in: 2, zoot:3}}, self.vps);
+			}, /16875/);
+		},
+
+		"should return a Let expression": function() {
+			var x = Expression.parseOperand({$let: {vars: {a:{$const:123}}, in: 2}}, this.vps);
+			assert(x instanceof LetExpression);
+			assert(x._subExpression instanceof ConstantExpression);
+			assert.strictEqual(x._subExpression.getValue(), 2);
+			assert(x._variables[0].expression instanceof ConstantExpression);
+			assert.strictEqual(x._variables[0].expression.getValue(), 123);
+		},
+
+		"should show we collect multiple vars": function() {
+			var x = Expression.parseOperand({$let: {vars: {a:{$const:1}, b:{$const:2}, c:{$const:3}}, in: 2}}, this.vps);
+			assert.strictEqual(x._variables[0].expression.getValue(), 1);
+			assert.strictEqual(x._variables[1].expression.getValue(), 2);
+			assert.strictEqual(x._variables[2].expression.getValue(), 3);
+		},
+
+	},
+
+	"#optimize()": {
+
+		beforeEach: function() {
+			this.testInOpt = function(expr, expected) {
+				assert(expr._subExpression instanceof ConstantExpression, "should have $const subexpr");
+				assert.strictEqual(expr._subExpression.operands.length, 0);
+				assert.strictEqual(expr._subExpression.getValue(), expected);
+			};
+			this.testVarOpt = function(expr, expected) {
+				var varExpr = expr._variables[0].expression;
+				assert(varExpr instanceof ConstantExpression, "should have $const first var");
+				assert.strictEqual(varExpr.getValue(), expected);
+			};
+		},
+
+		"should optimize to subexpression if no variables": function() {
+			var x = Expression.parseOperand({$let:{vars:{}, in:{$multiply:[2,3]}}}, this.vps).optimize();
+			assert(x instanceof ConstantExpression, "should become $const");
+			assert.strictEqual(x.getValue(), 6);
+		},
+
+		"should optimize variables": function() {
+			var x = Expression.parseOperand({$let:{vars:{a:{$multiply:[5,4]}}, in:{$const:6}}}, this.vps).optimize();
+			this.testVarOpt(x, 20);
+		},
+
+		"should optimize subexpressions if there are variables": function() {
+			var x = Expression.parseOperand({$let:{vars:{a:{$multiply:[5,4]}}, in: {$multiply:[2,3]}}}, this.vps).optimize();
+			this.testInOpt(x, 6);
+			this.testVarOpt(x, 20);
+		},
+
+	},
+
+	"#serialize()": {
+
+		"should serialize variables and the subexpression": function() {
+			var s = Expression.parseOperand({$let: {vars: {a:{$const:1}, b:{$const:2}}, in: {$multiply: [2,3]}}}, this.vps).optimize().serialize("zoot");
+			var expected = {$let:{vars:{a:{$const:1},b:{$const:2}},in:{$const:6}}};
+			assert.deepEqual(s, expected);
+		},
+
+	},
+
+	"#evaluate()": {
+
+		"should perform the evaluation for variables and the subexpression": function() {
+			var x = Expression.parseOperand({$let: {vars: {a: '$in1', b: '$in2'}, in: { $multiply: ["$$a", "$$b"] }}}, this.vps).optimize();
+			var	y = x.evaluate(new Variables(10, {in1: 6, in2: 7}));
+			assert.equal(y, 42);
+		},
+
+	},
+
+	"#addDependencies()": {
+
+		"should add dependencies": function() {
+			var expr = Expression.parseOperand({$let: {vars: {a: {$multiply:['$a','$b']}}, in: {$multiply: ['$c','$d']}}}, this.vps);
+			var deps = new DepsTracker();
+			expr.addDependencies(deps);
+			assert.equal(Object.keys(deps.fields).length, 4);
+			assert('a' in deps.fields);
+			assert('b' in deps.fields);
+			assert('c' in deps.fields);
+			assert('d' in deps.fields);
+			assert.strictEqual(deps.needWholeDocument, false);
+			assert.strictEqual(deps.needTextScore, false);
+		},
+
+	},
+
+	"The Gauntlet": {
+
+		"example from http://docs.mongodb.org/manual/reference/operator/aggregation/let/": function() {
+			var x = Expression.parseOperand(
+				{$let: { vars: { total: { $add: [ '$price', '$tax' ] },	discounted: { $cond: { if: '$applyDiscount', then: 0.9, else: 1 } }}, in: { $multiply: [ '$$total', '$$discounted' ] }}},
+				this.vps).optimize();
+			var y;
+			y = x.evaluate(new Variables(10, {price: 90, tax: 0.05}));
+			assert.equal(y, 90.05);
+			y = x.evaluate(new Variables(10, {price: 90, tax: 0.05, applyDiscount: 1}));
+			assert.equal(y, 90.05 * 0.9);
+		},
+
+	},
+
+};
+
+
+if (!module.parent)(new (require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 9 - 9
test/lib/pipeline/expressions/MapExpression_test.js

@@ -19,16 +19,16 @@ exports.MapExpression = {
 
 
 	"constructor()": {
 	"constructor()": {
 
 
-		"should accept 4 arguments": function () {
+		"should accept 4 arguments": function() {
 			new MapExpression(1, 2, 3, 4);
 			new MapExpression(1, 2, 3, 4);
 		},
 		},
 
 
-		"should accept only 4 arguments": function () {
-			assert.throws(function () { new MapExpression(); });
-			assert.throws(function () { new MapExpression(1); });
-			assert.throws(function () { new MapExpression(1, 2); });
-			assert.throws(function () { new MapExpression(1, 2, 3); });
-			assert.throws(function () { new MapExpression(1, 2, 3, 4, 5); });
+		"should accept only 4 arguments": function() {
+			assert.throws(function() { new MapExpression(); });
+			assert.throws(function() { new MapExpression(1); });
+			assert.throws(function() { new MapExpression(1, 2); });
+			assert.throws(function() { new MapExpression(1, 2, 3); });
+			assert.throws(function() { new MapExpression(1, 2, 3, 4, 5); });
 		},
 		},
 
 
 	},
 	},
@@ -47,7 +47,7 @@ exports.MapExpression = {
 				optimized = expr.optimize();
 				optimized = expr.optimize();
 			assert.strictEqual(optimized, expr, "should be same reference");
 			assert.strictEqual(optimized, expr, "should be same reference");
 			assert.deepEqual(expressionToJson(optimized._input), {$const:[1,2,3]});
 			assert.deepEqual(expressionToJson(optimized._input), {$const:[1,2,3]});
-			assert.deepEqual(expressionToJson(optimized._each), constify({$add:["$$i","$$i",1,2]}));
+			assert.deepEqual(expressionToJson(optimized._each), constify({$add:["$$i","$$i",3]}));
 		},
 		},
 
 
 	},
 	},
@@ -92,7 +92,7 @@ exports.MapExpression = {
 
 
 	"#addDependencies()": {
 	"#addDependencies()": {
 
 
-		"should add dependencies to both $map.input and $map.in": function () {
+		"should add dependencies to both $map.input and $map.in": function() {
 			var spec = {$map:{
 			var spec = {$map:{
 					input: "$inputArray",
 					input: "$inputArray",
 					as: "i",
 					as: "i",

+ 0 - 0
test/lib/pipeline/expressions/MillisecondExpression.js → test/lib/pipeline/expressions/MillisecondExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/MinuteExpression.js → test/lib/pipeline/expressions/MinuteExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/ModExpression.js → test/lib/pipeline/expressions/ModExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/MonthExpression.js → test/lib/pipeline/expressions/MonthExpression_test.js


+ 37 - 33
test/lib/pipeline/expressions/NaryExpression_test.js

@@ -3,11 +3,16 @@
 var assert = require("assert"),
 var assert = require("assert"),
 	VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
 	VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
 	VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
 	VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
+	Expression = require("../../../../lib/pipeline/expressions/Expression"),
 	NaryExpression = require("../../../../lib/pipeline/expressions/NaryExpression"),
 	NaryExpression = require("../../../../lib/pipeline/expressions/NaryExpression"),
 	ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
 	ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
 	FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
 	FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
-	Expression = require("../../../../lib/pipeline/expressions/Expression"),
-	utils = require("./utils");
+	AndExpression = require("../../../../lib/pipeline/expressions/AndExpression"), //jshint ignore:line
+	AddExpression = require("../../../../lib/pipeline/expressions/AddExpression"), //jshint ignore:line
+	DepsTracker = require("../../../../lib/pipeline/DepsTracker"),
+	utils = require("./utils"),
+	constify = utils.constify,
+	expressionToJson = utils.expressionToJson;
 
 
 
 
 // Mocha one-liner to make these tests self-hosted
 // Mocha one-liner to make these tests self-hosted
@@ -16,13 +21,11 @@ if(!module.parent)return(require.cache[__filename]=null,(new(require("mocha"))({
 
 
 // A dummy child of NaryExpression used for testing
 // A dummy child of NaryExpression used for testing
 var Testable = (function(){
 var Testable = (function(){
-	// CONSTRUCTOR
 	var klass = function Testable(isAssociativeAndCommutative){
 	var klass = function Testable(isAssociativeAndCommutative){
 		this._isAssociativeAndCommutative = isAssociativeAndCommutative;
 		this._isAssociativeAndCommutative = isAssociativeAndCommutative;
 		base.call(this);
 		base.call(this);
 	}, base = NaryExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 	}, base = NaryExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-	// MEMBERS
 	proto.evaluateInternal = function evaluateInternal(vars) {
 	proto.evaluateInternal = function evaluateInternal(vars) {
 		// Just put all the values in a list.  This is not associative/commutative so
 		// Just put all the values in a list.  This is not associative/commutative so
 		// the results will change if a factory is provided and operations are reordered.
 		// the results will change if a factory is provided and operations are reordered.
@@ -54,14 +57,15 @@ var Testable = (function(){
 		var idGenerator = new VariablesIdGenerator(),
 		var idGenerator = new VariablesIdGenerator(),
 			vps = new VariablesParseState(idGenerator),
 			vps = new VariablesParseState(idGenerator),
 			testable = Testable.create(haveFactory);
 			testable = Testable.create(haveFactory);
-		operands.forEach(function(element) {
+		for (var i = 0, l = operands.length; i < l; i++) {
+			var element = operands[i];
 			testable.addOperand(Expression.parseOperand(element, vps));
 			testable.addOperand(Expression.parseOperand(element, vps));
-		});
+		}
 		return testable;
 		return testable;
 	};
 	};
 
 
 	proto.assertContents = function assertContents(expectedContents) {
 	proto.assertContents = function assertContents(expectedContents) {
-		assert.deepEqual(utils.constify({$testable:expectedContents}), utils.expressionToJson(this));
+		assert.deepEqual(constify({$testable:expectedContents}), expressionToJson(this));
 	};
 	};
 
 
 	return klass;
 	return klass;
@@ -103,26 +107,23 @@ exports.NaryExpression = {
 		var testable = Testable.create();
 		var testable = Testable.create();
 
 
 		var assertDependencies = function assertDependencies(expectedDeps, expr) {
 		var assertDependencies = function assertDependencies(expectedDeps, expr) {
-			var deps = {}, //TODO: new DepsTracker
-				depsJson = [];
+			var deps = new DepsTracker();
 			expr.addDependencies(deps);
 			expr.addDependencies(deps);
-			deps.forEach(function(dep) {
-				depsJson.push(dep);
-			});
+			var depsJson = Object.keys(deps.fields).sort();
 			assert.deepEqual(depsJson, expectedDeps);
 			assert.deepEqual(depsJson, expectedDeps);
-			assert.equal(deps.needWholeDocument, false);
-			assert.equal(deps.needTextScore, false);
+			assert.strictEqual(deps.needWholeDocument, false);
+			assert.strictEqual(deps.needTextScore, false);
 		};
 		};
 
 
 		// No arguments.
 		// No arguments.
 		assertDependencies([], testable);
 		assertDependencies([], testable);
 
 
 		// Add a constant argument.
 		// Add a constant argument.
-		testable.addOperand(new ConstantExpression(1));
+		testable.addOperand(ConstantExpression.create(1));
 		assertDependencies([], testable);
 		assertDependencies([], testable);
 
 
 		// Add a field path argument.
 		// Add a field path argument.
-		testable.addOperand(new FieldPathExpression("ab.c"));
+		testable.addOperand(FieldPathExpression.create("ab.c"));
 		assertDependencies(["ab.c"], testable);
 		assertDependencies(["ab.c"], testable);
 
 
 		// Add an object expression.
 		// Add an object expression.
@@ -131,7 +132,7 @@ exports.NaryExpression = {
 			ctx = new Expression.ObjectCtx({isDocumentOk:true}),
 			ctx = new Expression.ObjectCtx({isDocumentOk:true}),
 			vps = new VariablesParseState(new VariablesIdGenerator());
 			vps = new VariablesParseState(new VariablesIdGenerator());
 		testable.addOperand(Expression.parseObject(specElement, ctx, vps));
 		testable.addOperand(Expression.parseObject(specElement, ctx, vps));
-		assertDependencies(["ab.c", "r", "x"]);
+		assertDependencies(["ab.c", "r", "x"], testable);
 	},
 	},
 
 
 	/** Serialize to an object. */
 	/** Serialize to an object. */
@@ -159,7 +160,7 @@ exports.NaryExpression = {
 		var spec = [{$and:[]}, "$abc"],
 		var spec = [{$and:[]}, "$abc"],
 			testable = Testable.createFromOperands(spec);
 			testable = Testable.createFromOperands(spec);
 		testable.assertContents(spec);
 		testable.assertContents(spec);
-		assert.deepEqual(testable.serialize(), testable.optimize().serialize());
+		assert.strictEqual(testable, testable.optimize());
 		testable.assertContents([true, "$abc"]);
 		testable.assertContents([true, "$abc"]);
 	},
 	},
 
 
@@ -169,8 +170,8 @@ exports.NaryExpression = {
 			testable = Testable.createFromOperands(spec);
 			testable = Testable.createFromOperands(spec);
 		testable.assertContents(spec);
 		testable.assertContents(spec);
 		var optimized = testable.optimize();
 		var optimized = testable.optimize();
-		assert.notDeepEqual(testable.serialize(), optimized.serialize());
-		assert.deepEqual({$const:[1,2]}, utils.expressionToJson(optimized));
+		assert.notStrictEqual(testable, optimized);
+		assert.deepEqual({$const:[1,2]}, expressionToJson(optimized));
 	},
 	},
 
 
 	"NoFactoryOptimize": {
 	"NoFactoryOptimize": {
@@ -201,7 +202,7 @@ exports.NaryExpression = {
 		// The constant expressions are evaluated separately and placed at the end.
 		// The constant expressions are evaluated separately and placed at the end.
 		var testable = Testable.createFromOperands([55, 66, "$path"], true),
 		var testable = Testable.createFromOperands([55, 66, "$path"], true),
 			optimized = testable.optimize();
 			optimized = testable.optimize();
-		assert.deepEqual(utils.constify({$testable:["$path", [55, 66]]}), utils.expressionToJson(optimized));
+		assert.deepEqual(constify({$testable:["$path", [55, 66]]}), expressionToJson(optimized));
 	},
 	},
 
 
 	/** Factory optimization flattens nested operators of the same type. */
 	/** Factory optimization flattens nested operators of the same type. */
@@ -209,18 +210,23 @@ exports.NaryExpression = {
 		var testable = Testable.createFromOperands(
 		var testable = Testable.createFromOperands(
 				[55, "$path", {$add:[5,6,"$q"]}, 66],
 				[55, "$path", {$add:[5,6,"$q"]}, 66],
 			true);
 			true);
+		// Add a nested $testable operand.
 		testable.addOperand(Testable.createFromOperands(
 		testable.addOperand(Testable.createFromOperands(
 				[99, 100, "$another_path"],
 				[99, 100, "$another_path"],
-			true));
+			true)
+		);
 		var optimized = testable.optimize();
 		var optimized = testable.optimize();
 		assert.deepEqual(
 		assert.deepEqual(
-			utils.constify({$testable:[
-					"$path",
-					{$add:["$q", 11]},
-					"$another_path",
-					[55, 66, [99, 100]]
-				]}),
-			utils.expressionToJson(optimized));
+			constify({$testable:[
+				// non constant parts
+				"$path",
+				{$add:["$q", 11]},
+				"$another_path",
+				// constant part last
+				[55, 66, [99, 100]]
+			]}),
+			expressionToJson(optimized)
+		);
 	},
 	},
 
 
 	/** Three layers of factory optimization are flattened. */
 	/** Three layers of factory optimization are flattened. */
@@ -231,10 +237,8 @@ exports.NaryExpression = {
 		top.addOperand(nested);
 		top.addOperand(nested);
 		var optimized = top.optimize();
 		var optimized = top.optimize();
 		assert.deepEqual(
 		assert.deepEqual(
-			utils.constify({
-				$testable: ["$a", "$b", "$c", [1, 2, [3, 4, [5, 6]]]]
-			}),
-			utils.expressionToJson(optimized)
+			constify({$testable: ["$a", "$b", "$c", [1, 2, [3, 4, [5, 6]]]]}),
+			expressionToJson(optimized)
 		);
 		);
 	},
 	},
 
 

+ 0 - 0
test/lib/pipeline/expressions/ObjectExpression.js → test/lib/pipeline/expressions/ObjectExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SecondExpression.js → test/lib/pipeline/expressions/SecondExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SetDifferenceExpression.js → test/lib/pipeline/expressions/SetDifferenceExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SetEqualsExpression.js → test/lib/pipeline/expressions/SetEqualsExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SetIntersectionExpression.js → test/lib/pipeline/expressions/SetIntersectionExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SetIsSubsetExpression.js → test/lib/pipeline/expressions/SetIsSubsetExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SetUnionExpression.js → test/lib/pipeline/expressions/SetUnionExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SizeExpression.js → test/lib/pipeline/expressions/SizeExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/StrcasecmpExpression.js → test/lib/pipeline/expressions/StrcasecmpExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/SubtractExpression.js → test/lib/pipeline/expressions/SubtractExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/VariablesIdGenerator.js → test/lib/pipeline/expressions/VariablesIdGenerator_test.js


+ 0 - 0
test/lib/pipeline/expressions/VariablesParseState.js → test/lib/pipeline/expressions/VariablesParseState_test.js


+ 0 - 0
test/lib/pipeline/expressions/Variables.js → test/lib/pipeline/expressions/Variables_test.js


+ 0 - 0
test/lib/pipeline/expressions/WeekExpression.js → test/lib/pipeline/expressions/WeekExpression_test.js


+ 0 - 0
test/lib/pipeline/expressions/YearExpression.js → test/lib/pipeline/expressions/YearExpression_test.js


+ 4 - 4
test/lib/pipeline/expressions/utils.js

@@ -31,10 +31,10 @@ var utils = module.exports = {
 
 
 	//SKIPPED: toJson
 	//SKIPPED: toJson
 
 
-    /**
-     * Convert Expression to BSON.
-     * @method expressionToJson
-     */
+	/**
+	 * Convert Expression to BSON.
+	 * @method expressionToJson
+	 */
 	expressionToJson: function expressionToJson(expr) {
 	expressionToJson: function expressionToJson(expr) {
 		return expr.serialize(false);
 		return expr.serialize(false);
 	},
 	},