Explorar el Código

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

Kyle P Davis hace 11 años
padre
commit
ea89876425
Se han modificado 83 ficheros con 3653 adiciones y 2674 borrados
  1. 2 2
      lib/pipeline/ValueSet.js
  2. 33 41
      lib/pipeline/accumulators/Accumulator.js
  3. 27 36
      lib/pipeline/accumulators/AddToSetAccumulator.js
  4. 29 33
      lib/pipeline/accumulators/AvgAccumulator.js
  5. 10 18
      lib/pipeline/accumulators/FirstAccumulator.js
  6. 16 13
      lib/pipeline/accumulators/LastAccumulator.js
  7. 25 31
      lib/pipeline/accumulators/MinMaxAccumulator.js
  8. 20 25
      lib/pipeline/accumulators/PushAccumulator.js
  9. 25 18
      lib/pipeline/accumulators/SumAccumulator.js
  10. 1 1
      lib/pipeline/expressions/SetDifferenceExpression.js
  11. 1 1
      lib/pipeline/expressions/SetEqualsExpression.js
  12. 1 1
      lib/pipeline/expressions/SetIntersectionExpression.js
  13. 1 1
      lib/pipeline/expressions/SetIsSubsetExpression.js
  14. 1 1
      lib/pipeline/expressions/SetUnionExpression.js
  15. 28 56
      lib/pipeline/matcher/AllElemMatchOp.js
  16. 6 0
      lib/pipeline/matcher/AndMatchExpression.js
  17. 26 30
      lib/pipeline/matcher/ArrayFilterEntries.js
  18. 30 37
      lib/pipeline/matcher/ArrayMatchingMatchExpression.js
  19. 2 6
      lib/pipeline/matcher/AtomicMatchExpression.js
  20. 40 58
      lib/pipeline/matcher/ComparisonMatchExpression.js
  21. 0 73
      lib/pipeline/matcher/Context.js
  22. 18 24
      lib/pipeline/matcher/ElemMatchObjectMatchExpression.js
  23. 6 13
      lib/pipeline/matcher/ElemMatchValueMatchExpression.js
  24. 26 35
      lib/pipeline/matcher/ElementPath.js
  25. 5 4
      lib/pipeline/matcher/EqualityMatchExpression.js
  26. 6 9
      lib/pipeline/matcher/ExistsMatchExpression.js
  27. 19 9
      lib/pipeline/matcher/FalseMatchExpression.js
  28. 15 13
      lib/pipeline/matcher/GTEMatchExpression.js
  29. 14 13
      lib/pipeline/matcher/GTMatchExpression.js
  30. 55 74
      lib/pipeline/matcher/InMatchExpression.js
  31. 0 58
      lib/pipeline/matcher/IndexKeyMatchableDocument.js
  32. 14 12
      lib/pipeline/matcher/LTEMatchExpression.js
  33. 14 12
      lib/pipeline/matcher/LTMatchExpression.js
  34. 39 76
      lib/pipeline/matcher/LeafMatchExpression.js
  35. 53 43
      lib/pipeline/matcher/ListOfMatchExpression.js
  36. 42 49
      lib/pipeline/matcher/MatchDetails.js
  37. 139 88
      lib/pipeline/matcher/MatchExpression.js
  38. 117 52
      lib/pipeline/matcher/MatchExpressionParser.js
  39. 14 279
      lib/pipeline/matcher/Matcher2.js
  40. 10 16
      lib/pipeline/matcher/ModMatchExpression.js
  41. 5 0
      lib/pipeline/matcher/NorMatchExpression.js
  42. 9 16
      lib/pipeline/matcher/NotMatchExpression.js
  43. 6 0
      lib/pipeline/matcher/OrMatchExpression.js
  44. 42 25
      lib/pipeline/matcher/RegexMatchExpression.js
  45. 14 17
      lib/pipeline/matcher/SizeMatchExpression.js
  46. 0 55
      lib/pipeline/matcher/TagData.js
  47. 112 0
      lib/pipeline/matcher/TextMatchExpression.js
  48. 25 0
      lib/pipeline/matcher/TextMatchExpressionParser.js
  49. 97 98
      lib/pipeline/matcher/TypeMatchExpression.js
  50. 4 2
      package.json
  51. 68 71
      test/lib/pipeline/accumulators/AddToSetAccumulator.js
  52. 192 73
      test/lib/pipeline/accumulators/AvgAccumulator.js
  53. 77 55
      test/lib/pipeline/accumulators/FirstAccumulator.js
  54. 71 44
      test/lib/pipeline/accumulators/LastAccumulator.js
  55. 0 78
      test/lib/pipeline/accumulators/MaxAccumulator.js
  56. 0 78
      test/lib/pipeline/accumulators/MinAccumulator.js
  57. 206 0
      test/lib/pipeline/accumulators/MinMaxAccumulator.js
  58. 97 77
      test/lib/pipeline/accumulators/PushAccumulator.js
  59. 240 78
      test/lib/pipeline/accumulators/SumAccumulator.js
  60. 0 131
      test/lib/pipeline/matcher/AllElemMatchOp.js
  61. 128 0
      test/lib/pipeline/matcher/AllElemMatchOp_test.js
  62. 8 8
      test/lib/pipeline/matcher/AndMatchExpression.js
  63. 0 50
      test/lib/pipeline/matcher/ComparisonMatchExpression.js
  64. 110 0
      test/lib/pipeline/matcher/ComparisonMatchExpression_test.js
  65. 1 3
      test/lib/pipeline/matcher/ElementPath.js
  66. 48 24
      test/lib/pipeline/matcher/EqualityMatchExpression.js
  67. 7 7
      test/lib/pipeline/matcher/ExistsMatchExpression.js
  68. 51 0
      test/lib/pipeline/matcher/FalseMatchExpression.js
  69. 67 32
      test/lib/pipeline/matcher/GTEMatchExpression.js
  70. 70 38
      test/lib/pipeline/matcher/GTMatchExpression.js
  71. 46 41
      test/lib/pipeline/matcher/InMatchExpression.js
  72. 85 42
      test/lib/pipeline/matcher/LTEMatchExpression.js
  73. 93 52
      test/lib/pipeline/matcher/LTMatchExpression.js
  74. 72 0
      test/lib/pipeline/matcher/ListOfMatchExpression.js
  75. 62 0
      test/lib/pipeline/matcher/MatchDetails.js
  76. 91 34
      test/lib/pipeline/matcher/MatchExpressionParser.js
  77. 78 0
      test/lib/pipeline/matcher/MatchExpression_test.js
  78. 86 0
      test/lib/pipeline/matcher/Matcher2.js
  79. 4 4
      test/lib/pipeline/matcher/NorMatchExpression.js
  80. 4 4
      test/lib/pipeline/matcher/OrMatchExpression.js
  81. 216 0
      test/lib/pipeline/matcher/RegexMatchExpression.js
  82. 59 0
      test/lib/pipeline/matcher/TextMatchExpression_test.js
  83. 72 76
      test/lib/pipeline/matcher/TypeMatchExpression.js

+ 2 - 2
lib/pipeline/expressions/ValueSet.js → lib/pipeline/ValueSet.js

@@ -1,8 +1,8 @@
 "use strict";
 
 /**
- * Somewhat mimics the ValueSet in mongo (std::set<Value>)
- * @class valueSet
+ * A set of values (i.e., `typedef unordered_set<Value, Value::Hash> ValueSet;`)
+ * @class ValueSet
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor

+ 33 - 41
lib/pipeline/accumulators/Accumulator.js

@@ -1,8 +1,7 @@
 "use strict";
 
 /**
- * A base class for all pipeline accumulators. Uses NaryExpression as a base class.
- *
+ * A base class for all pipeline accumulators.
  * @class Accumulator
  * @namespace mungedb-aggregate.pipeline.accumulators
  * @module mungedb-aggregate
@@ -10,66 +9,59 @@
  **/
 var Accumulator = module.exports = function Accumulator(){
 	if (arguments.length !== 0) throw new Error("zero args expected");
-	this._memUsageBytes = 0;
 	base.call(this);
 }, klass = Accumulator, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// DEPENDENCIES
-// var Value = require("../Value"),
-
-proto.memUsageForSorter = function memUsageForSorter() {
-	return this._memUsageBytes;
-};
-
-proto.getFactory = function getFactory(){
-	return klass;	// using the ctor rather than a separate .create() method
-};
-
 /** Process input and update internal state.
- * merging should be true when processing outputs from getValue(true).
+ *  merging should be true when processing outputs from getValue(true).
+ *  @method process
+ *  @param input {Value}
+ *  @param merging {Boolean}
  */
-proto.process = function process(input, merging){
+proto.process = function process(input, merging) {
 	this.processInternal(input, merging);
 };
 
-proto.toJSON = function toJSON(isExpressionRequired){
-	var rep = {};
-	rep[this.getOpName()] = this.operands[0].toJSON(isExpressionRequired);
-	return rep;
+/** Marks the end of the evaluate() phase and return accumulated result.
+ *  toBeMerged should be true when the outputs will be merged by process().
+ *  @method getValue
+ *  @param toBeMerged {Boolean}
+ *  @return {Value}
+ */
+proto.getValue = function getValue(toBeMerged) {
+	throw new Error("You need to define this function on your accumulator");
 };
 
 /**
- * If this function is not overridden in the sub classes,
- * then throw an error
- *
- **/
+ * The name of the op as used in a serialization of the pipeline.
+ * @method getOpName
+ * @return {String}
+ */
 proto.getOpName = function getOpName() {
 	throw new Error("You need to define this function on your accumulator");
 };
 
+//NOTE: DEVIATION FROM MONGO: not implementing this
+//int memUsageForSorter() const {}
+
 /**
- * If this function is not overridden in the sub classes,
- * then throw an error
- *
- **/
-proto.getValue = function getValue(toBeMerged) {
+ * Reset this accumulator to a fresh state ready to receive input.
+ * @method reset
+ */
+proto.reset = function reset() {
 	throw new Error("You need to define this function on your accumulator");
 };
 
 /**
- * If this function is not overridden in the sub classes,
- * then throw an error
- *
- **/
+ * Update subclass's internal state based on input
+ * @method processInternal
+ * @param input {Value}
+ * @param merging {Boolean}
+ */
 proto.processInternal = function processInternal(input, merging) {
 	throw new Error("You need to define this function on your accumulator");
 };
 
-/**
- * If this function is not overridden in the sub classes,
- * then throw an error
- *
- **/
-proto.reset = function reset() {
-	throw new Error("You need to define this function on your accumulator");
-};
+//NOTE: DEVIATION FROM MONGO: not implementing this
+// /// subclasses are expected to update this as necessary
+// int _memUsageBytes;

+ 27 - 36
lib/pipeline/accumulators/AddToSetAccumulator.js

@@ -6,54 +6,45 @@
  * @namespace mungedb-aggregate.pipeline.accumulators
  * @module mungedb-aggregate
  * @constructor
-**/
-var AddToSetAccumulator = module.exports = function AddToSetAccumulator(/* ctx */){
+ */
+var AddToSetAccumulator = module.exports = function AddToSetAccumulator() {
 	if (arguments.length !== 0) throw new Error("zero args expected");
-	this.set = [];
-	//this.itr = undefined; /* Shoudln't need an iterator for the set */
-	//this.ctx = undefined; /* Not using the context object currently as it is related to sharding */
+	this.reset();
 	base.call(this);
 }, klass = AddToSetAccumulator, Accumulator = require("./Accumulator"), base = Accumulator, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
-
-// DEPENDENCIES
-var Value = require("../Value");
-
-
-// MEMBER FUNCTIONS
-
-proto.getOpName = function getOpName(){
-	return "$addToSet";
-};
-
-proto.getFactory = function getFactory(){
-	return klass;	// using the ctor rather than a separate .create() method
-};
-
-
-proto.contains = function contains(value) {
-	var set = this.set;
-	for (var i = 0, l = set.length; i < l; ++i) {
-		if (Value.compare(set[i], value) === 0) {
-			return true;
-		}
-	}
-	return false;
-};
+var ValueSet = require("../ValueSet");
 
 proto.processInternal = function processInternal(input, merging) {
-	if (! this.contains(input)) {
-		this.set.push(input);
+	if (!merging) {
+		if (input !== undefined) {
+			this.set.insert(input);
+		}
+	} else {
+		// If we're merging, we need to take apart the arrays we
+		// receive and put their elements into the array we are collecting.
+		// If we didn't, then we'd get an array of arrays, with one array
+		// from each merge source.
+		if (!Array.isArray(input)) throw new Error("Assertion failure");
+
+		for (var i = 0, l = input.length; i < l; i++) {
+			this.set.insert(input[i]);
+		}
 	}
 };
 
-proto.getValue = function getValue(toBeMerged) {
-	return this.set;
+proto.getValue = function getValue(toBeMerged) { //jshint ignore:line
+	return this.set.values();
 };
 
 proto.reset = function reset() {
-	this.set = [];
+	this.set = new ValueSet();
 };
 
+klass.create = function create() {
+	return new AddToSetAccumulator();
+};
 
+proto.getOpName = function getOpName() {
+	return "$addToSet";
+};

+ 29 - 33
lib/pipeline/accumulators/AvgAccumulator.js

@@ -8,57 +8,53 @@
  * @constructor
  **/
 var AvgAccumulator = module.exports = function AvgAccumulator(){
-	this.subTotalName = "subTotal";
-	this.countName = "count";
-	this.totalIsANumber = true;
-	this.total = 0;
-	this.count = 0;
+	this.reset();
 	base.call(this);
 }, klass = AvgAccumulator, Accumulator = require("./Accumulator"), base = Accumulator, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
-
-// DEPENDENCIES
 var Value = require("../Value");
 
-// MEMBER FUNCTIONS
+var SUB_TOTAL_NAME = "subTotal";
+var COUNT_NAME = "count";
+
 proto.processInternal = function processInternal(input, merging) {
 	if (!merging) {
-		if (typeof input !== "number") {
-			return;
-		}
-		this.total += input;
-		this.count += 1;
+		// non numeric types have no impact on average
+		if (typeof input != "number") return;
+
+		this._total += input;
+		this._count += 1;
 	} else {
-		Value.verifyDocument(input);
-		this.total += input[this.subTotalName];
-		this.count += input[this.countName];
+		// We expect an object that contains both a subtotal and a count.
+		// This is what getValue(true) produced below.
+		if (!(input instanceof Object)) throw new Error("Assertion error");
+		this._total += input[SUB_TOTAL_NAME];
+		this._count += input[COUNT_NAME];
 	}
 };
 
-proto.getValue = function getValue(toBeMerged){
+klass.create = function create() {
+	return new AvgAccumulator();
+};
+
+proto.getValue = function getValue(toBeMerged) {
 	if (!toBeMerged) {
-		if (this.totalIsANumber && this.count > 0) {
-			return this.total / this.count;
-		} else if (this.count === 0) {
-			return 0;
-		} else {
-			throw new Error("$sum resulted in a non-numeric type");
-		}
+		if (this._count === 0)
+			return 0.0;
+		return this._total / this._count;
 	} else {
-		var ret = {};
-		ret[this.subTotalName] = this.total;
-		ret[this.countName] = this.count;
-
-		return ret;
+		var doc = {};
+		doc[SUB_TOTAL_NAME] = this._total;
+		doc[COUNT_NAME] = this._count;
+		return doc;
 	}
 };
 
 proto.reset = function reset() {
-	this.total = 0;
-	this.count = 0;
+	this._total = 0;
+	this._count = 0;
 };
 
-proto.getOpName = function getOpName(){
+proto.getOpName = function getOpName() {
 	return "$avg";
 };

+ 10 - 18
lib/pipeline/accumulators/FirstAccumulator.js

@@ -9,30 +9,15 @@
  **/
 var FirstAccumulator = module.exports = function FirstAccumulator(){
 	if (arguments.length !== 0) throw new Error("zero args expected");
+	this.reset();
 	base.call(this);
-	this._haveFirst = false;
-	this._first = undefined;
 }, klass = FirstAccumulator, base = require("./Accumulator"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
-
-// MEMBER FUNCTIONS
-proto.getOpName = function getOpName(){
-	return "$first";
-};
-
-proto.getFactory = function getFactory(){
-	return klass;	// using the ctor rather than a separate .create() method
-};
-
-
 proto.processInternal = function processInternal(input, merging) {
-	/* only remember the first value seen */
+	// only remember the first value seen
 	if (!this._haveFirst) {
-		// can't use pValue.missing() since we want the first value even if missing
 		this._haveFirst = true;
 		this._first = input;
-		//this._memUsageBytes = sizeof(*this) + input.getApproximateSize() - sizeof(Value);
 	}
 };
 
@@ -43,5 +28,12 @@ proto.getValue = function getValue(toBeMerged) {
 proto.reset = function reset() {
 	this._haveFirst = false;
 	this._first = undefined;
-	this._memUsageBytes = 0;
+};
+
+klass.create = function create() {
+	return new FirstAccumulator();
+};
+
+proto.getOpName = function getOpName() {
+	return "$first";
 };

+ 16 - 13
lib/pipeline/accumulators/LastAccumulator.js

@@ -1,32 +1,35 @@
 "use strict";
 
-/** 
- * Constructor for LastAccumulator, wraps SingleValueAccumulator's constructor and finds the last document
+/**
+ * Accumulator for getting last value
  * @class LastAccumulator
  * @namespace mungedb-aggregate.pipeline.accumulators
  * @module mungedb-aggregate
  * @constructor
  **/
 var LastAccumulator = module.exports = function LastAccumulator(){
+	if (arguments.length !== 0) throw new Error("zero args expected");
+	this.reset();
 	base.call(this);
-	this.value = undefined;
 }, klass = LastAccumulator, base = require("./Accumulator"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
+proto.processInternal = function processInternal(input, merging) {
+	// always remember the last value seen
+	this._last = input;
+};
 
-// MEMBER FUNCTIONS
-proto.processInternal = function processInternal(input, merging){
-	this.value = input;
+proto.getValue = function getValue(toBeMerged) {
+	return this._last;
 };
 
-proto.getValue = function getValue() {
-	return this.value;
+proto.reset = function reset() {
+	this._last = undefined;
 };
 
-proto.getOpName = function getOpName(){
-	return "$last";
+klass.create = function create() {
+	return new LastAccumulator();
 };
 
-proto.reset = function reset() {
-	this.value = undefined;
+proto.getOpName = function getOpName(){
+	return "$last";
 };

+ 25 - 31
lib/pipeline/accumulators/MinMaxAccumulator.js

@@ -1,55 +1,49 @@
 "use strict";
 
 /**
- * Constructor for MinMaxAccumulator, wraps SingleValueAccumulator's constructor and adds flag to track whether we have started or not
+ * Accumulator to get the min or max value
  * @class MinMaxAccumulator
  * @namespace mungedb-aggregate.pipeline.accumulators
  * @module mungedb-aggregate
  * @constructor
  **/
-var MinMaxAccumulator = module.exports = function MinMaxAccumulator(sense){
-	if (arguments.length > 1) throw new Error("expects a single value");
+var MinMaxAccumulator = module.exports = function MinMaxAccumulator(theSense){
+	if (arguments.length != 1) throw new Error("expects a single value");
+	this._sense = theSense; // 1 for min, -1 for max; used to "scale" comparison
 	base.call(this);
-	this.sense = sense; /* 1 for min, -1 for max; used to "scale" comparison */
-	if (this.sense !== 1 && this.sense !== -1) throw new Error("this should never happen");
+	if (this._sense !== 1 && this._sense !== -1) throw new Error("Assertion failure");
 }, klass = MinMaxAccumulator, base = require("./Accumulator"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
-
-// DEPENDENCIES
 var Value = require("../Value");
 
-// MEMBER FUNCTIONS
-proto.getOpName = function getOpName(){
-	if (this.sense == 1) return "$min";
-	return "$max";
-};
-
-klass.createMin = function createMin(){
-	return new MinMaxAccumulator(1);
+proto.processInternal = function processInternal(input, merging) {
+	// nullish values should have no impact on result
+	if (!(input === undefined || input === null)) {
+		// compare with the current value; swap if appropriate
+		var cmp = Value.compare(this._val, input) * this._sense;
+		if (cmp > 0 || this._val === undefined) { // missing is lower than all other values
+			this._val = input;
+		}
+	}
 };
 
-klass.createMax = function createMax(){
-	return new MinMaxAccumulator(-1);
+proto.getValue = function getValue(toBeMerged) {
+	return this._val;
 };
 
 proto.reset = function reset() {
-	this.value = undefined;
+	this._val = undefined;
 };
 
-proto.getValue = function getValue(toBeMerged) {
-	return this.value;
+klass.createMin = function createMin(){
+	return new MinMaxAccumulator(1);
 };
 
-proto.processInternal = function processInternal(input, merging) {
-	// if this is the first value, just use it
-	if (!this.hasOwnProperty('value')) {
-		this.value = input;
-	} else {
-		// compare with the current value; swap if appropriate
-		var cmp = Value.compare(this.value, input) * this.sense;
-		if (cmp > 0) this.value = input;
-	}
+klass.createMax = function createMax(){
+	return new MinMaxAccumulator(-1);
+};
 
-	return this.value;
+proto.getOpName = function getOpName() {
+	if (this._sense == 1) return "$min";
+	return "$max";
 };

+ 20 - 25
lib/pipeline/accumulators/PushAccumulator.js

@@ -8,44 +8,39 @@
  * @constructor
  **/
 var PushAccumulator = module.exports = function PushAccumulator(){
+	if (arguments.length !== 0) throw new Error("zero args expected");
 	this.values = [];
 	base.call(this);
 }, klass = PushAccumulator, Accumulator = require("./Accumulator"), base = Accumulator, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// NOTE: Skipping the create function, using the constructor instead
-
-// MEMBER FUNCTIONS
-proto.getValue = function getValue(toBeMerged){
-	return this.values;
-};
-
-proto.getOpName = function getOpName(){
-	return "$push";
-};
-
-proto.getFactory = function getFactory(){
-	return klass;	// using the ctor rather than a separate .create() method
-};
-
-
 proto.processInternal = function processInternal(input, merging) {
 	if (!merging) {
 		if (input !== undefined) {
 			this.values.push(input);
-			//_memUsageBytes += input.getApproximateSize();
 		}
-	}
-	else {
+	} else {
 		// If we're merging, we need to take apart the arrays we
 		// receive and put their elements into the array we are collecting.
 		// If we didn't, then we'd get an array of arrays, with one array
 		// from each merge source.
-		if (!(input instanceof Array)) throw new Error("input is not an Array during merge in PushAccumulator:35");
-
-		this.values = this.values.concat(input);
+		if (!Array.isArray(input)) throw new Error("Assertion failure");
 
-		//for (size_t i=0; i < vec.size(); i++) {
-			//_memUsageBytes += vec[i].getApproximateSize();
-		//}
+		Array.prototype.push.apply(this.values, input);
 	}
 };
+
+proto.getValue = function getValue(toBeMerged) {
+	return this.values;
+};
+
+proto.reset = function reset() {
+	this.values = [];
+};
+
+klass.create = function create() {
+	return new PushAccumulator();
+};
+
+proto.getOpName = function getOpName() {
+	return "$push";
+};

+ 25 - 18
lib/pipeline/accumulators/SumAccumulator.js

@@ -1,37 +1,44 @@
 "use strict";
 
-/** 
- * Accumulator for summing a field across documents
+/**
+ * Accumulator for summing values
  * @class SumAccumulator
  * @namespace mungedb-aggregate.pipeline.accumulators
  * @module mungedb-aggregate
  * @constructor
  **/
-var SumAccumulator = module.exports = function SumAccumulator(){
-	this.total = 0;
-	this.count = 0;
-	this.totalIsANumber = true;
+var SumAccumulator = module.exports = function SumAccumulator() {
+	if (arguments.length !== 0) throw new Error("zero args expected");
+	this.reset();
 	base.call(this);
-}, klass = SumAccumulator, Accumulator = require("./Accumulator"), base = Accumulator, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-// NOTE: Skipping the create function, using the constructor instead
+}, klass = SumAccumulator, base = require("./Accumulator"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// MEMBER FUNCTIONS
 proto.processInternal = function processInternal(input, merging) {
-	if(typeof input === "number"){ // do nothing with non-numeric types
-		this.totalIsANumber = true;
-		this.total += input;
+	// do nothing with non numeric types
+	if (typeof input !== "number"){
+		if (input !== undefined && input !== null) { //NOTE: DEVIATION FROM MONGO: minor fix for 0-like values
+			this.isNumber = false;
+		}
+		return;
 	}
-	this.count++;
+	this.total += input;
+};
 
-	return 0;
+klass.create = function create() {
+	return new SumAccumulator();
 };
 
-proto.getValue = function getValue(toBeMerged){
-	if (this.totalIsANumber) {
+proto.getValue = function getValue(toBeMerged) {
+	if (this.isNumber) {
 		return this.total;
+	} else {
+		throw new Error("$sum resulted in a non-numeric type; massert code 16000");
 	}
-	throw new Error("$sum resulted in a non-numeric type");
+};
+
+proto.reset = function reset() {
+	this.isNumber = true;
+	this.total = 0;
 };
 
 proto.getOpName = function getOpName(){

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

@@ -14,7 +14,7 @@ var SetDifferenceExpression = module.exports = function SetDifferenceExpression(
 
 var Value = require("../Value"),
 	Expression = require("./Expression"),
-	ValueSet = require("./ValueSet");
+	ValueSet = require("../ValueSet");
 
 proto.evaluateInternal = function evaluateInternal(vars) {
 	var lhs = this.operands[0].evaluateInternal(vars),

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

@@ -14,7 +14,7 @@ var SetEqualsExpression = module.exports = function SetEqualsExpression() {
 
 var Value = require("../Value"),
 	Expression = require("./Expression"),
-	ValueSet = require("./ValueSet");
+	ValueSet = require("../ValueSet");
 
 proto.validateArguments = function validateArguments(args) {
 	if (args.length < 2)

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

@@ -14,7 +14,7 @@ var SetIntersectionExpression = module.exports = function SetIntersectionExpress
 
 var Value = require("../Value"),
 	Expression = require("./Expression"),
-	ValueSet = require("./ValueSet");
+	ValueSet = require("../ValueSet");
 
 proto.evaluateInternal = function evaluateInternal(vars) {
 	var n = this.operands.length,

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

@@ -17,7 +17,7 @@ var Value = require("../Value"),
 	Expression = require("./Expression"),
 	NaryExpression = require("./NaryExpression"),
 	ConstantExpression = require("./ConstantExpression"),
-	ValueSet = require("./ValueSet");
+	ValueSet = require("../ValueSet");
 
 function setIsSubsetHelper(lhs, rhs) { //NOTE: vector<Value> &lhs, ValueSet &rhs
 	// do not shortcircuit when lhs.size() > rhs.size()

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

@@ -14,7 +14,7 @@ var SetUnionExpression = module.exports = function SetUnionExpression() {
 
 var Value = require("../Value"),
 	Expression = require("./Expression"),
-	ValueSet = require("./ValueSet");
+	ValueSet = require("../ValueSet");
 
 proto.evaluateInternal = function evaluateInternal(vars) {
 	var unionedSet = new ValueSet(),

+ 28 - 56
lib/pipeline/matcher/AllElemMatchOp.js

@@ -2,8 +2,7 @@
 
 var MatchExpression = require('./MatchExpression');
 
-
-// Autogenerated by cport.py on 2013-09-17 14:37
+// From expression_array.h
 var AllElemMatchOp = module.exports = function AllElemMatchOp(){
 	base.call(this);
 	this._matchType = 'ALL';
@@ -11,26 +10,15 @@ var AllElemMatchOp = module.exports = function AllElemMatchOp(){
 	this._list = [];
 }, klass = AllElemMatchOp, base =  MatchExpression , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// DEPENDENCIES
 var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes,
 	ElementPath = require('./ElementPath.js');
 
-// File: expression_array.h lines: 175-175
-//         ElementPath _elementPath;
-
+// ElementPath _elementPath
 proto._elementPath = undefined;
-
-
-// File: expression_array.h lines: 176-176
-//         std::vector< const ArrayMatchingMatchExpression* > _list;
-
+// std::vector< MatchExpression* > _list;
 proto._list = undefined;
-
-
-// File: expression_array.h lines: 174-174
-//         StringData _path;
-
+// StringData _path;
 proto._path = undefined;
 
 /**
@@ -40,18 +28,16 @@ proto._path = undefined;
  * @param anArray
  *
  */
-proto._allMatch = function _allMatch( anArray ){ //  const BSONObj& anArray
-	// File: expression_array.cpp lines: 208-215
-	if(this._list.length === 0) { return false; }
+proto._allMatch = function _allMatch(anArray) {
+	if (this._list.length === 0) return false;
 
 	for (var i = 0; i < this._list.length; i++) {
-		if( ! this._list[i].matchesArray( anArray, null ) ) { return false; }
+		if (!this._list[i].matchesArray(anArray, null)) return false;
 	}
 
 	return true;
 };
 
-
 /**
  *
  * This method adds a new expression to the internal array of expression
@@ -59,13 +45,11 @@ proto._allMatch = function _allMatch( anArray ){ //  const BSONObj& anArray
  * @param expr
  *
  */
-proto.add = function add( expr ){//  const ArrayMatchingMatchExpression* expr
-	// File: expression_array.cpp lines: 184-186
+proto.add = function add(expr) {
 	if (!expr) throw new Error("AllElemMatchOp:add#68 failed to verify expr");
 	this._list.push(expr);
 };
 
-
 /**
  *
  * Writes a debug string for this object
@@ -73,15 +57,13 @@ proto.add = function add( expr ){//  const ArrayMatchingMatchExpression* expr
  * @param level
  *
  */
-proto.debugString = function debugString( level ){ //   StringBuilder& debug, int level
-	// File: expression_array.cpp lines: 219-224
+proto.debugString = function debugString(level) {
 	console.debug(this._debugAddSpace(level) + this._path + " AllElemMatchOp: " + this._path + '\n');
 	for (var i = 0; i < this._list.length; i++) {
 		this._list[i].debugString(level +1);
 	}
 };
 
-
 /**
  *
  * checks if this expression is == to the other
@@ -89,29 +71,27 @@ proto.debugString = function debugString( level ){ //   StringBuilder& debug, in
  * @param other
  *
  */
-proto.equivalent = function equivalent( other ){//  const MatchExpression* other
-// File: expression_array.cpp lines: 227-242
+proto.equivalent = function equivalent(other) {
 	if (this.matchType() != other.matchType()) {
 		return false;
 	}
 
-	if( this._path != other._path ) {
+	if (this._path != other._path) {
 		return false;
 	}
 
-	if( this._list.length != other._list.length ) {
+	if (this._list.length != other._list.length) {
 		return false;
 	}
+
 	for (var i = 0; i < this._list.length; i++) {
-		if ( !this._list[i].equivalent( other._list[i] ) ) {
+		if (!this._list[i].equivalent(other._list[i])) {
 			return false;
 		}
 	}
 	return true;
 };
 
-
-
 /**
  *
  * gets the specified item from the list
@@ -119,12 +99,10 @@ proto.equivalent = function equivalent( other ){//  const MatchExpression* other
  * @param i
  *
  */
-proto.getChild = function getChild( i ){ //  size_t i
-// File: expression_array.h lines: 167-166
+proto.getChild = function getChild(i) {
 	return this._list[i];
 };
 
-
 /**
  *
  * Initialize the necessary items
@@ -132,11 +110,10 @@ proto.getChild = function getChild( i ){ //  size_t i
  * @param path
  *
  */
-proto.init = function init( path ){ //  const StringData& path
-// File: expression_array.cpp lines: 177-181
+proto.init = function init(path) {
 	this._path = path;
-	var s = this._elementPath.init( this._path );
-	this._elementPath.setTraverseLeafArray( false );
+	var s = this._elementPath.init(this._path);
+	this._elementPath.setTraverseLeafArray(false);
 	return s;
 };
 
@@ -148,17 +125,18 @@ proto.init = function init( path ){ //  const StringData& path
  * @param details
  *
  */
-proto.matches = function matches(doc, details){
-	// File: expression_array.cpp lines: 189-198
+proto.matches = function matches(doc, details) {
 	var self = this,
 		checker = function(element) {
-			if (!(element instanceof Array))
+			if (!(element instanceof Array)) {
 				return false;
+			}
 
 			//var amIRoot = (element.length === 0);
 
-			if (self._allMatch(element))
+			if (self._allMatch(element)) {
 				return true;
+			}
 
 			/*
 			if (!amIRoot && details && details.needRecord() {
@@ -177,15 +155,13 @@ proto.matches = function matches(doc, details){
  * @param e
  *
  */
-proto.matchesSingleElement = function matchesSingleElement( e ){ //  const BSONElement& e
-	// File: expression_array.cpp lines: 201-205
+proto.matchesSingleElement = function matchesSingleElement(e) {
 	if (!(e instanceof Array)) {
 		return false;
 	}
 	return this._allMatch(e);
 };
 
-
 /**
  *
  * return the length of the internal array
@@ -193,8 +169,7 @@ proto.matchesSingleElement = function matchesSingleElement( e ){ //  const BSONE
  * @param
  *
  */
-proto.numChildren = function numChildren( /*  */ ){
-// File: expression_array.h lines: 166-165
+proto.numChildren = function numChildren() {
 	return this._list.length;
 };
 
@@ -206,8 +181,7 @@ proto.numChildren = function numChildren( /*  */ ){
  * @param
  *
  */
-proto.path = function path( /*  */ ){
-// File: expression_array.h lines: 169-168
+proto.path = function path() {
 	return this._path;
 };
 
@@ -219,11 +193,9 @@ proto.path = function path( /*  */ ){
  * @param
  *
  */
-proto.shallowClone = function shallowClone( /*  */ ){
-// File: expression_array.h lines: 145-152
+proto.shallowClone = function shallowClone() {
 	var e = new AllElemMatchOp();
-	e.init( this._path );
+	e.init(this._path);
 	e._list = this._list.slice(0);
 	return e;
 };
-

+ 6 - 0
lib/pipeline/matcher/AndMatchExpression.js

@@ -78,5 +78,11 @@ proto.shallowClone = function shallowClone( /*  */ ){
 	for (var i = 0; i < this.numChildren(); i++) {
 		e.add(this.getChild(i).shallowClone());
 	}
+
+	if (this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
+
+	return e; // Return the shallow copy.
 };
 

+ 26 - 30
lib/pipeline/matcher/ArrayFilterEntries.js

@@ -1,7 +1,7 @@
 "use strict";
-var Value = require('../Value');
+var Value = require('../Value'),
+	ErrorCodes = require('../../Errors').ErrorCodes;
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var ArrayFilterEntries = module.exports = function ArrayFilterEntries(){
 	this._hasNull = false;
 	this._hasEmptyArray = false;
@@ -10,19 +10,12 @@ var ArrayFilterEntries = module.exports = function ArrayFilterEntries(){
 }, klass = ArrayFilterEntries, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-// File: expression_leaf.h lines: 266-266
 proto._equalities = undefined;
 
-
-// File: expression_leaf.h lines: 265-265
 proto._hasEmptyArray = undefined;
 
-
-// File: expression_leaf.h lines: 264-264
 proto._hasNull = undefined;
 
-
-// File: expression_leaf.h lines: 267-267
 proto._regexes = undefined;
 
 
@@ -34,14 +27,12 @@ proto._regexes = undefined;
  *
  */
 proto.addEquality = function addEquality(e) {
-	//File expression_leaf.cpp lines 369-387
-
-	if(e instanceof Object && Object.keys(e)[0][0] === '$'){
-		return {'code':'BAD_VALUE', 'desc':'cannot next $ under $in'};
-	}
-
 	if( e instanceof RegExp ) {
-		return {'code': 'BAD_VALUE', 'desc':'ArrayFilterEntries equality cannot be a regex'};
+		return {'code': ErrorCodes.BAD_VALUE, 'desc':'ArrayFilterEntries equality cannot be a regex'};
+	}
+	
+	if (e === undefined) {
+		return {'code': ErrorCodes.BAD_VALUE, 'desc':'ArrayFilterEntries equality cannot be undefined'};
 	}
 
 	if( e === null ) {
@@ -53,7 +44,7 @@ proto.addEquality = function addEquality(e) {
 	}
 
 	this._equalities.push( e );
-	return {'code':'OK'};
+	return {'code':ErrorCodes.OK};	
 };
 
 /**
@@ -64,9 +55,8 @@ proto.addEquality = function addEquality(e) {
  *
  */
 proto.addRegex = function addRegex(expr) {
-	// File: expression_leaf.cpp lines: 389-391
 	this._regexes.push( expr );
-	return {'code':'OK'};
+	return {'code':ErrorCodes.OK};
 };
 
 /**
@@ -77,7 +67,6 @@ proto.addRegex = function addRegex(expr) {
  *
  */
 proto.contains = function contains(elem) {
-	// File: expression_leaf.h lines: 249-248
 	for (var i = 0; i < this._equalities.length; i++) {
 		if(typeof(elem) == typeof(this._equalities[i])){
 			if(Value.compare(elem, this._equalities[i]) === 0) {
@@ -100,9 +89,13 @@ proto.copyTo = function copyTo(toFillIn) {
 	toFillIn._hasNull = this._hasNull;
 	toFillIn._hasEmptyArray = this._hasEmptyArray;
 	toFillIn._equalities = this._equalities.slice(0); // Copy array
+	
 	toFillIn._regexes = this._regexes.slice(0); // Copy array
+	for (var i = 0; i < this._regexes.length; i++){
+		toFillIn._regexes.push(this._regexes[i].shallowClone());
+	}
 };
-
+    
 /**
  *
  * Return the _equalities property
@@ -110,7 +103,6 @@ proto.copyTo = function copyTo(toFillIn) {
  *
  */
 proto.equalities = function equalities(){
-	// File: expression_leaf.h lines: 248-247
 	return this._equalities;
 };
 
@@ -122,9 +114,8 @@ proto.equalities = function equalities(){
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 394-404
 	if (this._hasNull != other._hasNull) {return false;}
-	if (this._regexes.length != other._regexes.length) {return false;}
+	if (this.size() != other.size()) {return false;}
 
 	for (var i = 0; i < this._regexes.length; i++) {
 		if ( !this._regexes[i].equivalent( other._regexes[i] ) ) {
@@ -141,7 +132,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.hasEmptyArray = function hasEmptyArray(){
-	// File: expression_leaf.h lines: 256-255
 	return this._hasEmptyArray;
 };
 
@@ -152,7 +142,6 @@ proto.hasEmptyArray = function hasEmptyArray(){
  *
  */
 proto.hasNull = function hasNull(){
-	// File: expression_leaf.h lines: 254-253
 	return this._hasNull;
 };
 
@@ -163,7 +152,6 @@ proto.hasNull = function hasNull(){
  *
  */
 proto.numRegexes = function numRegexes(){
-	// File: expression_leaf.h lines: 251-250
 	return this._regexes.length;
 };
 
@@ -175,7 +163,6 @@ proto.numRegexes = function numRegexes(){
  *
  */
 proto.regex = function regex(idx) {
-	// File: expression_leaf.h lines: 252-251
 	return this._regexes[idx];
 };
 
@@ -186,7 +173,6 @@ proto.regex = function regex(idx) {
  *
  */
 proto.singleNull = function singleNull(){
-	// File: expression_leaf.h lines: 255-254
 	return this.size() == 1 && this._hasNull;
 };
 
@@ -197,7 +183,17 @@ proto.singleNull = function singleNull(){
  *
  */
 proto.size = function size(){
-	// File: expression_leaf.h lines: 257-256
 	return this._equalities.length + this._regexes.length;
 };
 
+proto.debugString = function debugString(){
+	var debug = "[ ";
+	for (var i = 0; i < this._equalities.length; i++){
+		debug += this._equalities[i].toString() + " ";
+	}
+	for (var j = 0; j < this._regexes.length; j++){
+		debug += this._regexes[j].shortDebugString() + " ";
+	}
+	debug += "]";
+	return debug;
+};

+ 30 - 37
lib/pipeline/matcher/ArrayMatchingMatchExpression.js

@@ -2,11 +2,9 @@
 
 var MatchExpression = require('./MatchExpression');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var ArrayMatchingMatchExpression = module.exports = function ArrayMatchingMatchExpression(matchType){
 	base.call(this);
 	this._matchType = matchType;
-	// File: expression_array.h lines: 55-55
 	this._elementPath = new ElementPath();
 }, klass = ArrayMatchingMatchExpression, base = MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
@@ -15,35 +13,8 @@ var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes,
 	ElementPath = require('./ElementPath.js');
 
-// File: expression_array.h lines: 54-54
 proto._path = undefined;
 
-/**
- *
- * Check if the input element is equivalent to us
- * @method equivalent
- * @param
- *
- */
-proto.equivalent = function equivalent(other){
-	// File: expression_array.cpp lines: 63-79
-	if ( this._matchType != other._matchType)
-		return false;
-
-	var realOther = new ArrayMatchingMatchExpression(other);
-
-	if (this._path != realOther._path)
-		return false;
-
-	if (this.numChildren() != realOther.numChildren())
-		return false;
-
-	for (var i = 0; i < this.numChildren(); i++)
-		if (!this.getChild(i).equivalent(realOther.getChild(i)))
-			return false;
-	return true;
-};
-
 /**
  *
  * Initialize the input path as our element path
@@ -52,24 +23,21 @@ proto.equivalent = function equivalent(other){
  *
  */
 proto.initPath = function initPath(path){
-	// File: expression_array.cpp lines: 27-31
 	this._path = path;
 	var status = this._elementPath.init(this._path);
 	this._elementPath.setTraverseLeafArray(false);
 	return status;
 };
 
-
-
 /**
- *
+ * Deviation from mongo:
  * matches checks the input doc against the internal path to see if it is a match
  * @method matches
  * @param doc
  * @param details
  *
  */
-proto.matches = function matches(doc, details){
+ proto.matches = function matches(doc, details){
 	var self = this,
 		checker = function(element) {
 			// we got the whole path, now check it
@@ -99,12 +67,38 @@ proto.matches = function matches(doc, details){
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(element){
-	// File: expression_array.cpp lines: 56-59
-	if (!(element instanceof Array))
+	if (!(element instanceof Array)){
 		return false;
+	}
+
 	return this.matchesArray(element, null);
 };
 
+/**
+ *
+ * Check if the input element is equivalent to us
+ * @method equivalent
+ * @param
+ *
+ */
+proto.equivalent = function equivalent(other){
+	if ( this._matchType != other._matchType)
+		return false;
+
+	var realOther = new ArrayMatchingMatchExpression(other);
+
+	if (this._path != realOther._path)
+		return false;
+
+	if (this.numChildren() != realOther.numChildren())
+		return false;
+
+	for (var i = 0; i < this.numChildren(); i++)
+		if (! (this.getChild(i).equivalent(realOther.getChild(i)) ) )
+			return false;
+	return true;
+};
+
 /**
  *
  * return the internal path
@@ -113,7 +107,6 @@ proto.matchesSingleElement = function matchesSingleElement(element){
  *
  */
 proto.path = function path(){
-	// File: expression_array.h lines: 52-51
 	return this._path;
 };
 

+ 2 - 6
lib/pipeline/matcher/AtomicMatchExpression.js

@@ -2,6 +2,7 @@
 var MatchExpression = require('./MatchExpression');
 
 // Autogenerated by cport.py on 2013-09-17 14:37
+// File: expression.h
 var AtomicMatchExpression = module.exports = function AtomicMatchExpression(){
 	base.call(this);
 	this._matchType = 'ATOMIC';
@@ -15,7 +16,6 @@ var AtomicMatchExpression = module.exports = function AtomicMatchExpression(){
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression.cpp lines: 48-50
 	return this._debugAddSpace( level ) + "$atomic\n";
 };
 
@@ -27,8 +27,7 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression.h lines: 198-199
-	return other._matchType == 'ATOMIC';
+	return other._matchType === this._matchType;
 };
 
 /**
@@ -39,7 +38,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.matches = function matches(doc) {
-	// File: expression.h lines: 184-185
 	return true;
 };
 
@@ -51,7 +49,6 @@ proto.matches = function matches(doc) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression.h lines: 188-189
 	return true;
 };
 
@@ -62,6 +59,5 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression.h lines: 192-193
 	return new AtomicMatchExpression();
 };

+ 40 - 58
lib/pipeline/matcher/ComparisonMatchExpression.js

@@ -1,16 +1,20 @@
 "use strict";
-var LeafMatchExpression = require('./LeafMatchExpression.js');
-var Value = require('../Value');
+var LeafMatchExpression = require("./LeafMatchExpression.js");
+var Value = require("../Value");
 
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var ComparisonMatchExpression = module.exports = function ComparisonMatchExpression( type ){
+/**
+ * ComparisonMatchExpression
+ * @class ComparisonMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
+var ComparisonMatchExpression = module.exports = function ComparisonMatchExpression(type){
 	base.call(this);
 	this._matchType = type;
 }, klass = ComparisonMatchExpression, base =  LeafMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-// File: expression_leaf.h lines: 88-88
 proto._rhs = undefined;
 
 /**
@@ -21,34 +25,33 @@ proto._rhs = undefined;
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 135-154
-	var retStr = this._debugAddSpace( level ) + this.path() + " ";
-	switch (this._matchType){
-		case 'LT':
-			retStr += '$lt';
+	var retStr = this._debugAddSpace(level) + this.path() + " ";
+	switch (this._matchType) {
+		case "LT":
+			retStr += "$lt";
 			break;
-		case 'LTE':
-			retStr += '$lte';
+		case "LTE":
+			retStr += "$lte";
 			break;
-		case 'EQ':
-			retStr += '==';
+		case "EQ":
+			retStr += "==";
 			break;
-		case 'GT':
-			retStr += '$gt';
+		case "GT":
+			retStr += "$gt";
 			break;
-		case 'GTE':
-			retStr += '$gte';
+		case "GTE":
+			retStr += "$gte";
 			break;
 		default:
 			retStr += "Unknown comparison!";
 			break;
 	}
 
-	retStr += (this._rhs ? this._rhs.toString() : '?');
-	if ( this.getTag() ) {
+	retStr += (this._rhs ? this._rhs.toString() : "?");
+	if (this.getTag()) {
 		retStr += this.getTag().debugString();
 	}
-	return retStr + '\n';
+	return retStr + "\n";
 };
 
 /**
@@ -59,7 +62,6 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 53-61
 	if (other._matchType != this._matchType)  return false;
 	return this.path() === other.path() && Value.compare(this._rhs,other._rhs) === 0;
 };
@@ -70,8 +72,7 @@ proto.equivalent = function equivalent(other) {
  * @method getData
  *
  */
-proto.getData = function getData(){
-	// File: expression_leaf.h lines: 85-84
+proto.getData = function getData() {
 	return this._rhs;
 };
 
@@ -81,8 +82,7 @@ proto.getData = function getData(){
  * @method getRHS
  *
  */
-proto.getRHS = function getRHS(){
-	// File: expression_leaf.h lines: 79-78
+proto.getRHS = function getRHS() {
 	return this._rhs;
 };
 
@@ -95,15 +95,14 @@ proto.getRHS = function getRHS(){
  *
  */
 proto.init = function init(path,rhs) {
-	// File: expression_leaf.cpp lines: 65-87
 	this._rhs = rhs;
-	if ( (rhs instanceof Object && Object.keys(rhs).length === 0)) { return {'code':'BAD_VALUE', 'description':'Need a real operand'};}
+	if ((rhs instanceof Object && Object.keys(rhs).length === 0)) return {"code":"BAD_VALUE", "description":"Need a real operand"};
 
-	if ( rhs === undefined ) { return {'code':'BAD_VALUE', 'desc':'Cannot compare to undefined'};}
+	if (rhs === undefined) return {"code":"BAD_VALUE", "desc":"Cannot compare to undefined"};
 	if (!(this._matchType in {"LT":1, "LTE":1, "EQ":1, "GT":1, "GTE":1})) {
-		return {'code':'BAD_VALUE', 'description':'Bad match type for ComparisonMatchExpression'};
+		return {"code":"BAD_VALUE", "description":"Bad match type for ComparisonMatchExpression"};
 	}
-	return this.initPath( path );
+	return this.initPath(path);
 };
 
 /**
@@ -113,49 +112,32 @@ proto.init = function init(path,rhs) {
  * @param e
  *
  */
-proto.matchesSingleElement = function matchesSingleElement(e){
-	// File: expression_leaf.cpp lines: 91-132
-	if( typeof(e) != typeof(this._rhs) ){
-		if( this._rhs === null) {
-			if(this._matchType in {'EQ':1, 'LTE':1, 'GTE':1}){
-				if(e === undefined || e === null || (e instanceof Object && Object.keys(e).length === 0)) {
-					return true;
-				}
-			}
-			return false;
-		}
-		if((e === null || e === undefined) && (this._rhs ===null || this._rhs === undefined)) {
+proto.matchesSingleElement = function matchesSingleElement(e) {
+	if (Value.canonicalize(e) !== Value.canonicalize(this._rhs)) {
+		if (Value.canonicalize(e) + Value.canonicalize(this._rhs) === 5) {
 			return ["EQ","LTE","GTE"].indexOf(this._matchType) != -1;
 		}
 
-		if (this._rhs.constructor.name in {'MaxKey':1,'MinKey':1} ) {
-			return this._matchType != "EQ";
+		if (["MaxKey","MinKey"].indexOf(Value.getType(this._rhs)) != -1) {
+			return this._matchType !== "EQ";
 		}
 		return false;
 	}
 
-	if( this._rhs instanceof Array) {
-		if( this._matchType != 'EQ') {
-			return false;
-		}
-	}
-
-	var x = Value.compare( e, this._rhs );
+	var x = Value.compare(e, this._rhs);
 
-	switch( this._matchType ) {
+	switch(this._matchType) {
 		case "LT":
-			return x == -1;
+			return x < 0;
 		case "LTE":
 			return x <= 0;
 		case "EQ":
 			return x === 0;
 		case "GT":
-			return x === 1;
+			return x > 0;
 		case "GTE":
 			return x >= 0;
 		default:
 			throw new Error("Invalid comparison type evaluated.");
 	}
-	return false;
 };
-

+ 0 - 73
lib/pipeline/matcher/Context.js

@@ -1,73 +0,0 @@
-"use strict";
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var Context = module.exports = function (){
-
-}, klass = Context, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-// File: path.h lines: 62-62
-proto._arrayOffset = undefined;
-
-// File: path.h lines: 61-61
-proto._element = undefined;
-
-// File: path.h lines: 63-63
-proto._outerArray = undefined;
-
-/**
- *
- * Return the _arrayOffset property
- * @method arrayOffset
- *
- */
-proto.arrayOffset = function arrayOffset( ){
-	// File: path.h lines: 57-56
-	return this._arrayOffset;
-};
-
-/**
- *
- * Return the _element property
- * @method element
- *
- */
-proto.element = function element( ){
-	// File: path.h lines: 56-55
-	return this._element;
-};
-
-/**
- *
- * Return the _outerArray property
- * @method outerArray
- *
- */
-proto.outerArray = function outerArray( ){
-	// File: path.h lines: 58-57
-	return this._outerArray;
-};
-
-/**
- *
- * Set _element to a new empty object
- * @method reset
- *
- */
-proto.reset = function reset(){
-	// File: path.cpp lines: 37-38
-	this._element = {};
-};
-
-/**
- *
- * Set _arrayOffset property to the input element
- * @method setArrayOffset
- * @param e
- *
- */
-proto.setArrayOffset = function setArrayOffset(e){
-	// File: path.h lines: 54-53
-	this._arrayOffset = e;
-};
-
-

+ 18 - 24
lib/pipeline/matcher/ElemMatchObjectMatchExpression.js

@@ -1,12 +1,10 @@
 "use strict";
 var ArrayMatchingMatchExpression = require('./ArrayMatchingMatchExpression.js');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var ElemMatchObjectMatchExpression = module.exports = function ElemMatchObjectMatchExpression(){
 	base.call(this);
 }, klass = ElemMatchObjectMatchExpression, base = ArrayMatchingMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// File: expression_array.h lines: 77-77
 proto._sub = undefined;
 
 /**
@@ -17,21 +15,28 @@ proto._sub = undefined;
  *
  */
 proto.debugString = function debugString(level){
-	// File: expression_array.cpp lines: 106-109
-	var debug = this._debugAddSpace(level);
-	debug = debug + this.path() + " $elemMatch\n";
-	return debug + this._sub.debugString(level + 1);
+	return this._debugAddSpace( level ) + this.path() + " $elemMatch (obj) " + (this.getTag() ? this.getTag().debugString() : '') + this._exp._debugString( level + 1 );
+};
+
+/**
+ *
+ * Return 1 since we have a single "sub"
+ * @method numChildren
+ *
+ */
+proto.numChildren = function numChildren(){
+	return 1;
 };
 
 /**
  *
  * Return the _sub property
  * @method getChild
+ * @param i
  *
  */
-proto.getChild = function getChild(){
-	// File: expression_array.h lines: 74-73
-	return this._sub;
+proto.getChild = function getChild(i){
+	return this._sub.get();
 };
 
 /**
@@ -43,7 +48,6 @@ proto.getChild = function getChild(){
  *
  */
 proto.init = function init(path, sub){
-	// File: expression_array.cpp lines: 85-87
 	this._sub = sub;
 	return this.initPath(path);
 };
@@ -57,12 +61,11 @@ proto.init = function init(path, sub){
  *
  */
 proto.matchesArray = function matchesArray(anArray, details){
-	// File: expression_array.cpp lines: 90-103
 	for (var i in anArray) {
 		var inner = anArray[i];
 		if (!(inner instanceof Object))
 			continue;
-		if (this._sub.matchesBSON(inner, null)) {
+		if (this._sub.matchesJSON(inner, null)) {
 			if (details && details.needRecord()) {
 				details.setElemMatchKey(i);
 			}
@@ -72,17 +75,6 @@ proto.matchesArray = function matchesArray(anArray, details){
 	return false;
 };
 
-/**
- *
- * Return 1 since we have a single "sub"
- * @method numChildren
- *
- */
-proto.numChildren = function numChildren(){
-	// File: expression_array.h lines: 73-72
-	return 1;
-};
-
 /**
  *
  * clone this instance to a new one
@@ -90,8 +82,10 @@ proto.numChildren = function numChildren(){
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression_array.h lines: 65-68
 	var element = new ElemMatchObjectMatchExpression();
 	element.init(this.path(), this._sub.shallowClone());
+	if ( this.getTag() ){
+		element.setTag(this.getTag().clone());
+	}
 	return element;
 };

+ 6 - 13
lib/pipeline/matcher/ElemMatchValueMatchExpression.js

@@ -1,7 +1,6 @@
 "use strict";
 var ArrayMatchingMatchExpression = require('./ArrayMatchingMatchExpression.js');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var ElemMatchValueMatchExpression = module.exports = function ElemMatchValueMatchExpression(){
 	base.call(this);
 	this._matchType = 'ELEM_MATCH_VALUE';
@@ -12,7 +11,6 @@ var ElemMatchValueMatchExpression = module.exports = function ElemMatchValueMatc
 var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes;
 
-// File: expression_array.h lines: 108-108
 proto._subs = undefined;
 
 /**
@@ -23,7 +21,6 @@ proto._subs = undefined;
  *
  */
 proto._arrayElementMatchesAll = function _arrayElementMatchesAll(element){
-	// File: expression_array.cpp lines: 152-157
 	for (var i = 0; i < this._subs.length; i++ ) {
 		if (!this._subs[i].matchesSingleElement(element))
 			return false;
@@ -39,7 +36,6 @@ proto._arrayElementMatchesAll = function _arrayElementMatchesAll(element){
  *
  */
 proto.add = function add(sub){
-	// File: expression_array.cpp lines: 132-134
 	if (!sub) throw new Error(sub + " ElemMatchValueMatchExpression:36");
 	this._subs.push(sub);
 };
@@ -52,11 +48,9 @@ proto.add = function add(sub){
  *
  */
 proto.debugString = function debugString(level){
-	// File: expression_array.cpp lines: 160-165
-	var debug = this._debugAddSpace(level);
-	debug = debug + this.path() + " $elemMatch\n";
+	var debug = this._debugAddSpace(level) + this.path() + " $elemMatch (value)" + (this.getTag() ? this.getTag().debugString() : '') + "\n";
 	for (var i = 0; i < this._subs.length; i++) {
-		debug = debug + this._subs[i].debugString(level + 1);
+		debug += this._subs[i].debugString(level + 1);
 	}
 	return debug;
 };
@@ -69,7 +63,6 @@ proto.debugString = function debugString(level){
  *
  */
 proto.getChild = function getChild(i){
-	// File: expression_array.h lines: 103-102
 	return this._subs[i];
 };
 
@@ -82,7 +75,6 @@ proto.getChild = function getChild(i){
  *
  */
 proto.init = function init(path, sub){
-	// File: expression_array.cpp lines: 121-124
 	this.initPath(path);
 	if (sub)
 		this.add(sub);
@@ -98,7 +90,6 @@ proto.init = function init(path, sub){
  *
  */
 proto.matchesArray = function matchesArray(anArray, details){
-	// File: expression_array.cpp lines: 137-149
 	for (var i in anArray) {
 		var inner = anArray[i];
 
@@ -119,7 +110,6 @@ proto.matchesArray = function matchesArray(anArray, details){
  *
  */
 proto.numChildren = function numChildren(){
-	// File: expression_array.h lines: 102-101
 	return this._subs.length;
 };
 
@@ -130,11 +120,14 @@ proto.numChildren = function numChildren(){
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression_array.h lines: 91-97
 	var element = new ElemMatchValueMatchExpression();
 	element.init(this.path());
 	for (var i = 0; i < this._subs.length; ++i) {
 		element.add(this._subs[i].shallowClone());
 	}
+	var td = this.getTag();
+	if (td) {
+		element.setTag(td.clone());
+	}
 	return element;
 };

+ 26 - 35
lib/pipeline/matcher/ElementPath.js

@@ -1,23 +1,15 @@
 "use strict";
 
-var FieldRef = require('./FieldRef');
+var FieldRef = require('./FieldRef'),
+	ErrorCodes = require('../../Errors.js').ErrorCodes;
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var ElementPath = module.exports = function ElementPath(){
 	this._fieldRef = new FieldRef();
 	this._shouldTraverseLeafArray = false;
 }, klass = ElementPath, base =  Object  , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-
-// File: path.h lines: 41-41
-//         FieldRef _fieldRef;
-
 proto._fieldRef = undefined;
 
-
-// File: path.h lines: 42-42
-//         bool _shouldTraverseLeafArray;
-
 proto._shouldTraverseLeafArray = undefined;
 
 /**
@@ -29,7 +21,6 @@ proto._shouldTraverseLeafArray = undefined;
  * @param idxPathObj This is an object with a pathID element. This allows for pass by ref in calling function.
  * */
 klass.getFieldDottedOrArray = function getFieldDottedOrArray(doc, path, idxPathObj){
-	// File path_internal.cpp lines 31-72
 	if (path.numParts() === 0 ) { return doc; }
 
 	var res,curr = doc,
@@ -37,7 +28,7 @@ klass.getFieldDottedOrArray = function getFieldDottedOrArray(doc, path, idxPathO
 		partNum = 0;
 	while (partNum < path.numParts() && !stop) {
 		res = curr[path.getPart( partNum)];
-		if(res == {}){
+		if(res instanceof Object && Object.keys(res).length === 0){
 			stop = true;
 		} else if (res instanceof Object) {
 			curr = res;
@@ -53,7 +44,7 @@ klass.getFieldDottedOrArray = function getFieldDottedOrArray(doc, path, idxPathO
 
 	}
 
-	//idxPathObj.pathID = partNum;
+	idxPathObj.pathID = partNum;
 	return res;
 };
 
@@ -65,7 +56,6 @@ klass.getFieldDottedOrArray = function getFieldDottedOrArray(doc, path, idxPathO
  */
 
 klass.isAllDigits = function isAllDigits ( str ){
-	// File path_internal.cpp lines 23-29
 	var digitCheck = /\D/g;
 	if (digitCheck.exec(str) === null){ return true; }
 	return false;
@@ -82,8 +72,7 @@ klass.isAllDigits = function isAllDigits ( str ){
  * @param
  *
  */
-proto.fieldRef = function fieldRef( /*  */ ){
-// File: path.h lines: 37-36
+proto.fieldRef = function fieldRef(){
 	return this._fieldRef;
 };
 
@@ -95,11 +84,10 @@ proto.fieldRef = function fieldRef( /*  */ ){
  * @param path
  *
  */
-proto.init = function init( path ){  //  const StringData& path
-// File: path.cpp lines: 26-29
+proto.init = function init( path ){
 	this._shouldTraverseLeafArray = true;
 	this._fieldRef.parse( path );
-	return {'code':'OK'};
+	return {'code':ErrorCodes.OK};
 };
 
 
@@ -110,8 +98,7 @@ proto.init = function init( path ){  //  const StringData& path
  * @param
  *
  */
-proto.setTraverseLeafArray = function setTraverseLeafArray( b ){ //  bool b
-// File: path.h lines: 35-34
+proto.setTraverseLeafArray = function setTraverseLeafArray( b ){
 	this._shouldTraverseLeafArray = b;
 };
 
@@ -123,27 +110,27 @@ proto.setTraverseLeafArray = function setTraverseLeafArray( b ){ //  bool b
  * @param
  *
  */
-proto.shouldTraverseLeafArray = function shouldTraverseLeafArray( /*  */ ){
-// File: path.h lines: 38-37
+proto.shouldTraverseLeafArray = function shouldTraverseLeafArray( ){
 	return this._shouldTraverseLeafArray;
 };
 
+
 proto.objAtPath = function objAtPath(doc) {
 	return klass.objAtPath(doc, this._fieldRef._path);
 };
 
 klass.objAtPath = function objAtPath(doc, path) {
-	if(path.length === 0) {
+	if (path.length === 0) {
 		return doc;
 	}
-	if (path.length > 0 && Object.keys(doc).length === 0){
+	if (path.length > 0 && Object.keys(doc).length === 0) {
 		return {};
 	}
 	if (doc === null || doc === undefined) {
 		return doc;
 	}
 	var tpath = path.split('.');
-	return klass.objAtPath(doc[tpath[0]],tpath.slice(1).join('.'));
+	return klass.objAtPath(doc[tpath[0]], tpath.slice(1).join('.'));
 };
 
 
@@ -172,15 +159,14 @@ proto._matches = function _matches(doc, details, checker) {
  *
  */
 klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details, checker){
-	// File: expression_array.cpp lines: 34-53
 	var k, result, ii, il,
 		curr = doc,
 		item = doc;
 	for (k = 0; k < path.length; k++) {
-		if((curr instanceof Object) && (path[k] in curr)) {
+		if ((curr instanceof Object) && (path[k] in curr)) {
 			item = curr[path[k]];
 		}
-		if(path[k].length === 0)
+		if (path[k].length === 0)
 			continue;
 		item = curr[path[k]];
 		if (item instanceof Object && item.constructor === Object) {
@@ -195,7 +181,7 @@ klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details,
 			curr = item;
 			continue;
 		} else if (item instanceof Object && item.constructor === Array) {
-			if (k == path.length-1) {
+			if (k == path.length - 1) {
 				if ((shouldTraverseLeafArray) && (isNaN(parseInt(path[k], 10)))) {
 					for (ii = 0, il = item.length; ii < il; ii++) {
 						result = checker(item[ii]);
@@ -205,20 +191,20 @@ klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details,
 							return result;
 						}
 					}
-					if(item.length === 0)
+					if (item.length === 0)
 						return checker({});
-					
+
 				}
 				curr = item;
 				break; // this is the end of the path, so check this array
-			} else if (!(isNaN(parseInt(path[k+1], 10)))) {
+			} else if (!(isNaN(parseInt(path[k + 1], 10)))) {
 				curr = item;
 				continue; // the *next* path section is an item in the array so we don't check this whole array
 			}
 			// otherwise, check each item in the array against the rest of the path
-			for(ii = 0, il = item.length; ii < il; ii++){
+			for (ii = 0, il = item.length; ii < il; ii++) {
 				var subitem = item[ii];
-				if (subitem.constructor !== Object) continue;	// can't look for a subfield in a non-object value.
+				if (!subitem || subitem.constructor !== Object) continue;	// can't look for a subfield in a non-object value.
 				if (this._matches(subitem, path.slice(k), shouldTraverseLeafArray, null, checker)) { // check the item against the rest of the path
 					if (details && details.needRecord())
 						details.setElemMatchKey(ii.toString());
@@ -226,6 +212,11 @@ klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details,
 				}
 			}
 			return false; // checked all items in the array and found no matches
+		} else {
+
+			if ( details === undefined && item !== null && curr[path[k+1]] !== undefined) {
+				curr = curr[path[k+1]];
+			}
 		}
 	}
 	return checker(item);

+ 5 - 4
lib/pipeline/matcher/EqualityMatchExpression.js

@@ -4,8 +4,7 @@ var ComparisonMatchExpression = require('./ComparisonMatchExpression');
 
 // Autogenerated by cport.py on 2013-09-17 14:37
 var EqualityMatchExpression = module.exports = function EqualityMatchExpression(){
-	base.call(this);
-	this._matchType = 'EQ';
+	base.call(this,'EQ');
 }, klass = EqualityMatchExpression, base =  ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 /**
@@ -16,9 +15,11 @@ var EqualityMatchExpression = module.exports = function EqualityMatchExpression(
  *
  */
 proto.shallowClone = function shallowClone( /*  */ ){
-// File: expression_leaf.h lines: 98-101
 	var e = new EqualityMatchExpression();
 	e.init ( this.path(), this._rhs );
+	
+	if ( this.getTag() ) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 6 - 9
lib/pipeline/matcher/ExistsMatchExpression.js

@@ -1,7 +1,7 @@
 "use strict";
 var LeafMatchExpression = require('./LeafMatchExpression');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+// File: expression_leaf.cpp
 var ExistsMatchExpression = module.exports = function ExistsMatchExpression(){
 	base.call(this);
 	this._matchType = 'EXISTS';
@@ -15,7 +15,6 @@ var ExistsMatchExpression = module.exports = function ExistsMatchExpression(){
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 286-294
 	return this._debugAddSpace( level ) + this.path() + " exists" + (this.getTag() ? " " + this.getTag().debugString() : "") + "\n";
 };
 
@@ -27,8 +26,7 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 297-302
-	if(this._matchType != other._matchType)	{
+	if(this._matchType !== other._matchType) {
 		return false;
 	}
 	return this.path() == other.path();
@@ -43,7 +41,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.init = function init(path) {
-	// File: expression_leaf.cpp lines: 278-279
 	return this.initPath( path );
 };
 
@@ -55,12 +52,11 @@ proto.init = function init(path) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression_leaf.cpp lines: 282-283
-	if(typeof(e) == 'undefined')
+	if(typeof(e) === 'undefined')
 		return false;
 	if(e === null)
 		return true;
-	if(typeof(e) == 'object')
+	if(typeof(e) === 'object')
 		return (Object.keys(e).length > 0);
 	else
 		return true;
@@ -73,9 +69,10 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression_leaf.h lines: 220-223
 	var e = new ExistsMatchExpression();
 	e.init(this.path());
+	if (this.getTag())
+		e.setTag(this.getTag().clone());
 	return e;
 };
 

+ 19 - 9
lib/pipeline/matcher/FalseMatchExpression.js

@@ -1,9 +1,14 @@
 "use strict";
 var MatchExpression = require('./MatchExpression');
 
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var FalseMatchExpression = module.exports = function FalseMatchExpression(){
+/**
+ * A match expression that always returns false
+ * @class FalseMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ **/
+ var FalseMatchExpression = module.exports = function FalseMatchExpression(){
 	base.call(this);
 	this._matchType = 'ALWAYS_FALSE';
 }, klass = FalseMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
@@ -16,7 +21,6 @@ var FalseMatchExpression = module.exports = function FalseMatchExpression(){
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression.cpp lines: 53-55
 	return this._debugAddSpace( level ) + "$false\n";
 };
 
@@ -28,8 +32,7 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression.h lines: 222-223
-	return other._matchType === 'ALWAYS_FALSE';
+	return other._matchType === this._matchType;
 };
 
 /**
@@ -41,7 +44,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.matches = function matches(doc,details) {
-	// File: expression.h lines: 208-209
 	return false;
 };
 
@@ -53,7 +55,6 @@ proto.matches = function matches(doc,details) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression.h lines: 212-213
 	return false;
 };
 
@@ -64,7 +65,16 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression.h lines: 216-217
 	return new FalseMatchExpression();
 };
 
+/**
+ *
+ * append to JSON object
+ * @method shallowClone
+ *
+ */
+proto.toJson = function toJson(out){
+	out.$false = 1;
+	return out;
+};

+ 15 - 13
lib/pipeline/matcher/GTEMatchExpression.js

@@ -1,24 +1,26 @@
 "use strict";
 
-var ComparisonMatchExpression = require('./ComparisonMatchExpression');
+var ComparisonMatchExpression = require("./ComparisonMatchExpression");
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * File: matcher/expression_leaf.h
+ * @class GTEMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var GTEMatchExpression = module.exports = function GTEMatchExpression(){
-	base.call(this);
-	this._matchType = 'GTE';
-}, klass = GTEMatchExpression, base =  ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+	base.call(this, "GTE");
+}, klass = GTEMatchExpression, base = ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 /**
- *
- * Return a new instance of this class, with fields set the same as ourself
  * @method shallowClone
- * @param
- *
  */
-proto.shallowClone = function shallowClone( /*  */ ){
-// File: expression_leaf.h lines: 141-144
+proto.shallowClone = function shallowClone(){
 	var e = new GTEMatchExpression();
-	e.init( this.path(), this._rhs );
+	e.init(this.path(), this._rhs);
+	if(this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 14 - 13
lib/pipeline/matcher/GTMatchExpression.js

@@ -1,25 +1,26 @@
 "use strict";
 
-var ComparisonMatchExpression = require('./ComparisonMatchExpression.js');
+var ComparisonMatchExpression = require("./ComparisonMatchExpression.js");
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * File: matcher/expression_leaf.h
+ * @class GTMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var GTMatchExpression = module.exports = function GTMatchExpression(){
-	base.call(this);
-	this._matchType = 'GT';
+	base.call(this, "GT");
 }, klass = GTMatchExpression, base = ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-
 /**
- *
- * Return a new instance of this class, with fields set the same as ourself
  * @method shallowClone
- * @param
- *
  */
-proto.shallowClone = function shallowClone( /* */ ){
-	// File: expression_leaf.h lines: 130-133
+proto.shallowClone = function shallowClone(){
 	var e = new GTMatchExpression();
-	e.init( this.path(), this._rhs );
+	e.init(this.path(), this._rhs);
+	if(this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 55 - 74
lib/pipeline/matcher/InMatchExpression.js

@@ -1,7 +1,6 @@
 "use strict";
 var LeafMatchExpression = require('./LeafMatchExpression');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
 var InMatchExpression = module.exports = function InMatchExpression(){
 	base.call(this);
 	this._matchType = 'MATCH_IN';
@@ -13,9 +12,19 @@ var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes,
 	ArrayFilterEntries = require("./ArrayFilterEntries.js");
 
-// File: expression_leaf.h lines: 294-294
 proto._arrayEntries = null;
 
+/**
+ *
+ * Initialize the necessary items
+ * @method init
+ * @param path
+ *
+ */
+proto.init = function init(path) {
+	return this.initPath( path );
+};
+
 /**
  *
  * Check if the input element matches a real element
@@ -24,23 +33,13 @@ proto._arrayEntries = null;
  *
  */
 proto._matchesRealElement = function _matchesRealElement(e) {
-	// File: expression_leaf.cpp lines: 422-431
 	if(this._arrayEntries.contains(e)) { // array wrapper.... so no e "in" array
 		return true;
 	}
 
 	for (var i = 0; i < this._arrayEntries.numRegexes(); i++) {
-		if(e.match && e.match(this._arrayEntries.regex(i)._regex)) {
+		if ( this._arrayEntries.regex(i).matchesSingleElement( e ) )
 			return true;
-		} else if (e instanceof RegExp) {
-			if(e.toString() == this._arrayEntries.regex(i)._regex.toString()) {
-				return true;
-			}
-		}
-	}
-
-	if(typeof(e) == 'undefined') {
-		return true; // Every Set contains the Null Set, man.
 	}
 
 	return false;
@@ -48,15 +47,26 @@ proto._matchesRealElement = function _matchesRealElement(e) {
 
 /**
  *
- * Copy our array to the input array
- * @method copyTo
- * @param toFillIn
+ * Check if the input element matches
+ * @method matchesSingleElement
+ * @param e
  *
  */
-proto.copyTo = function copyTo(toFillIn) {
-	// File: expression_leaf.cpp lines: 481-483
-	toFillIn.init(this.path());
-	this._arrayEntries.copyTo( toFillIn._arrayEntries );
+proto.matchesSingleElement = function matchesSingleElement(e) {
+	if( this._arrayEntries.hasNull() && 
+		(	e === null ||
+			e === undefined ||
+			typeof(e) === 'object' && Object.keys(e).length === 0
+		)) 
+	{
+		return true;
+	}
+	
+	if (this._matchesRealElement( e )) {
+		return true;
+	}
+	
+	return false;
 };
 
 /**
@@ -67,8 +77,7 @@ proto.copyTo = function copyTo(toFillIn) {
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 455-463
-	return this._debugAddSpace( level ) + this.path() + ";$in: TODO " + (this.getTag() ? this.getTag().debugString() : '') + "\n";
+	return this._debugAddSpace( level ) + this.path() + " $in " + this._arrayEntries + (this.getTag() ? this.getTag().debugString() : '') + "\n";
 };
 
 /**
@@ -79,83 +88,55 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 466-472
 	if ( other._matchType != 'MATCH_IN' ) {
 		return false;
 	}
-	return this.path() == other.path() && this._arrayEntries.equivalent( other._arrayEntries );
-};
-
-/**
- *
- * Return the _arrayEntries property
- * @method getArrayFilterEntries
- *
- */
-proto.getArrayFilterEntries = function getArrayFilterEntries(){
-	// File: expression_leaf.h lines: 280-279
-	return this._arrayEntries;
+	return this.path() === other.path() && this._arrayEntries.equivalent( other._arrayEntries );
 };
 
 /**
  *
- * Return the _arrayEntries property
- * @method getData
+ * clone this instance to a new one
+ * @method shallowClone
  *
  */
-proto.getData = function getData(){
-	// File: expression_leaf.h lines: 290-289
-	return this._arrayEntries;
+proto.shallowClone = function shallowClone(){
+	var e = new InMatchExpression();
+	this.copyTo( e );
+	if ( this.getTag() ){
+		e.setTag(this.getTag().Clone());
+	}
+	return e;
 };
 
 /**
  *
- * Initialize the necessary items
- * @method init
- * @param path
+ * Copy our array to the input array
+ * @method copyTo
+ * @param toFillIn
  *
  */
-proto.init = function init(path) {
-	// File: expression_leaf.cpp lines: 418-419
-	return this.initPath( path );
+proto.copyTo = function copyTo(toFillIn) {
+	toFillIn.init(this.path());
+	this._arrayEntries.copyTo( toFillIn._arrayEntries );
 };
 
 /**
  *
- * Check if the input element matches
- * @method matchesSingleElement
- * @param e
+ * Return the _arrayEntries property
+ * @method getArrayFilterEntries
  *
  */
-proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression_leaf.cpp lines: 434-452
-	if( this._arrayEntries === null && typeof(e) == 'object' && Object.keys(e).length === 0) {
-		return true;
-	}
-	if (this._matchesRealElement( e )) {
-		return true;
-	}
-	/*if (e instanceof Array){
-		for (var i = 0; i < e.length; i++) {
-			if(this._matchesRealElement( e[i] )) {
-				return true;
-			}
-		}
-
-	}*/
-	return false;
+proto.getArrayFilterEntries = function getArrayFilterEntries(){
+	return this._arrayEntries;
 };
 
 /**
  *
- * clone this instance to a new one
- * @method shallowClone
+ * Return the _arrayEntries property
+ * @method getData
  *
  */
-proto.shallowClone = function shallowClone(){
-	// File: expression_leaf.cpp lines: 475-478
-	var e = new InMatchExpression();
-	this.copyTo( e );
-	return e;
+proto.getData = function getData(){
+	return this._arrayEntries;
 };
-

+ 0 - 58
lib/pipeline/matcher/IndexKeyMatchableDocument.js

@@ -1,58 +0,0 @@
-"use strict";
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var IndexKeyMatchableDocument = module.exports = function IndexKeyMatchableDocument(pattern, doc){
-	this._pattern = pattern;
-	this._doc = doc;
-}, klass = IndexKeyMatchableDocument, base =  Object  , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-// File: matcher.cpp lines: 52-52
-proto._doc = undefined;
-
-// File: matcher.cpp lines: 51-51
-proto._pattern = undefined;
-
-// File: matcher.cpp lines: 52-52
-proto._doc = undefined;
-
-// File: matcher.cpp lines: 51-51
-proto._pattern = undefined;
-
-/**
- *
- * get the element at the input path
- * @method _getElement
- * @param path
- *
- */
-proto._getElement = function _getElement(path){
-	// File: matcher.cpp lines: 63-77
-	var patternElement, docElement;
-
-	for (var i in this._pattern) {
-		patternElement = this._pattern[i];
-		//verify( docIterator.more() );
-		if(i >= this._doc.length) throw new Error("Ran out of docs in IndexKeyMatchableDocument:35");
-		docElement = this._doc[i];
-
-		if (path.equalsDottedField(patternElement.fieldName())) {
-			return docElement;
-		}
-	}
-
-	return {};
-};
-
-/**
- *
- * This method returns a JSON representation of the Document
- * @method toBSON
- * @param
- *
- */
-proto.toJSON = function toJSON(){
-	// File: matcher.cpp lines: 39-42
-	// TODO: this isn't quite correct because of dots
-	// don't think it'll ever be called though
-	return this._doc.replaceFieldNames(this._pattern);
-};

+ 14 - 12
lib/pipeline/matcher/LTEMatchExpression.js

@@ -1,24 +1,26 @@
 "use strict";
 
-var ComparisonMatchExpression = require('./ComparisonMatchExpression');
+var ComparisonMatchExpression = require("./ComparisonMatchExpression");
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * File: matcher/expression_leaf.h
+ * @class LTEMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var LTEMatchExpression = module.exports = function LTEMatchExpression(){
-	base.call(this);
-	this._matchType = 'LTE';
+	base.call(this, "LTE");
 }, klass = LTEMatchExpression, base = ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 /**
- *
- * Return a new instance of this class, with fields set the same as ourself
  * @method shallowClone
- * @param
- *
  */
-proto.shallowClone = function shallowClone( /* */ ){
-	// File: expression_leaf.h lines: 108-111
+proto.shallowClone = function shallowClone(){
 	var e = new LTEMatchExpression();
-	e.init( this.path(), this._rhs );
+	e.init(this.path(), this._rhs);
+	if(this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 14 - 12
lib/pipeline/matcher/LTMatchExpression.js

@@ -1,24 +1,26 @@
 "use strict";
 
-var ComparisonMatchExpression = require('./ComparisonMatchExpression');
+var ComparisonMatchExpression = require("./ComparisonMatchExpression");
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * File: matcher/expression_leaf.h
+ * @class LTMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var LTMatchExpression = module.exports = function LTMatchExpression(){
-	base.call(this);
-	this._matchType = 'LT';
+	base.call(this, "LT");
 }, klass = LTMatchExpression, base = ComparisonMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 /**
- *
- * Return a new instance of this class, with fields set the same as ourself
  * @method shallowClone
- * @param
- *
  */
-proto.shallowClone = function shallowClone( /* */ ){
-	// File: expression_leaf.h lines: 119-122
+proto.shallowClone = function shallowClone(){
 	var e = new LTMatchExpression();
-	e.init( this.path(), this._rhs );
+	e.init(this.path(), this._rhs);
+	if(this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 39 - 76
lib/pipeline/matcher/LeafMatchExpression.js

@@ -1,107 +1,70 @@
 "use strict";
 
-var MatchExpression = require('./MatchExpression');
-var ElementPath = require('./ElementPath');
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var LeafMatchExpression = module.exports = function LeafMatchExpression( type ){
-	base.call(this);
-	this._matchType = type;
-	this._elementPath = new ElementPath();
-}, klass = LeafMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var MatchExpression = require('./MatchExpression'),
+	ElementPath = require('./ElementPath');
 
+var LeafMatchExpression = module.exports = function LeafMatchExpression(type) {
+	base.call(this, type);
 
-// File: expression_leaf.h lines: 63-63
-//         ElementPath _elementPath;
+	this._elementPath = new ElementPath();
+}, klass = LeafMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
 
 proto._elementPath = undefined;
-
-
-// File: expression_leaf.h lines: 62-62
-//         StringData _path;
-
 proto._path = undefined;
 
-
 /**
  *
- * Initialize the ElementPath to the input path
- * @method initPath
- * @param path
- *
- */
-proto.initPath = function initPath( path ) { //  const StringData& path
-// File: expression_leaf.cpp lines: 31-33
-	this._path = path;
-	if(this._elementPath === undefined){
-		this._elementPath = new ElementPath();
-	}
-	return this._elementPath.init( this._path );
-};
-
-
-/**
+ * Checks whether the document matches against what was searched.
  *
- * Check whether the input doc matches
- * @method matches
  * @param doc
- *
+ * @param details
+ * @returns {*}
  */
-proto.matches = function matches( doc, details ) { //  const MatchableDocument* doc, MatchDetails* details
-	// File: expression_leaf.cpp lines: 37-48
+proto.matches = function matches(doc, details) {
 	var self = this,
 		checker = function(element) {
-			/*if (element instanceof Array) {
-				for (var i = 0; i < element.length; i++) {
-					if(self.matchesSingleElement(element[i])) {
-						if(details && details.needRecord()) {
-							details.setElemMatchKey(i.toString());
-						}
-						return true;
-					}
-				}
-				return false;
-			}*/
 			if (!self.matchesSingleElement(element)) {
 				return false;
 			}
-			/*
-			if( details && details.needRecord() && (element instanceof Array)) {
-				details.setElemMatchKey( docKeys[i+1] );
-			}
-			*/
+
 			return true;
 		};
+
 	return this._elementPath._matches(doc, details, checker);
-	/*
-	var tDoc = ElementPath.objAtPath( doc, this._path );
-	if(tDoc instanceof RegExp || typeof(tDoc) != 'object') {
-		return this.matchesSingleElement(tDoc);
-	}
-		if(!this.matchesSingleElement( tDoc[docKeys[i]] ))
-			continue;
-		if( details && details.needRecord() && (tDoc instanceof Array) && i < docKeys.length-1 ) {
-			console.log('test2');
-			details.setElemMatchKey( docKeys[i+1] );
-		}
-		return true;
-	}
-	return false;
-	*/
 };
 
-
-
+/**
+ *
+ * Overridable method for matching against a single element.
+ *
+ * @param e
+ * @returns {boolean}
+ */
+proto.matchesSingleElement = function matchesSingleElement(e) { return false; }; // The child class defines this method.
 
 /**
  *
- * Return the internal path
- * @method path
- * @param
+ * Return the internal path.
  *
+ * @returns {undefined|*|klass._path}
  */
-proto.path = function path( /*  */ ){
-// File: expression_leaf.h lines: 56-55
+proto.path = function path() {
 	return this._path;
 };
 
+/**
+ *
+ * Initialize the ElementPath to the input path.
+ *
+ * @param path
+ * @returns {*}
+ */
+proto.initPath = function initPath(path) {
+	this._path = path;
+
+	if (this._elementPath === undefined) {
+		this._elementPath = new ElementPath();
+	}
+
+	return this._elementPath.init(this._path);
+};

+ 53 - 43
lib/pipeline/matcher/ListOfMatchExpression.js

@@ -2,29 +2,21 @@
 
 var MatchExpression = require('./MatchExpression');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * Create a match expression to match a list of
+ * @class ListOfMatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var ListOfMatchExpression = module.exports = function ListOfMatchExpression(matchType){
 	base.call(this);
 	this._expressions = [];
 	this._matchType = matchType;
 }, klass = ListOfMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// File: expression_tree.h lines: 56-56
 proto._expressions = undefined;
 
-/**
- *
- * Print the debug info from each expression in the list
- * @method _debugList
- * @param level
- *
- */
-proto._debugList = function _debugList(level){
-	// File: expression_tree.cpp lines: 40-42
-	for (var i = 0; i < this._expressions.length; i++ )
-		this._expressions[i].debugString(level + 1); // debug only takes level now
-};
-
 /**
  *
  * Append a new expression to our list
@@ -33,10 +25,9 @@ proto._debugList = function _debugList(level){
  *
  */
 proto.add = function add( exp ){
-	// File: expression_tree.cpp lines: 34-36
 	// verify(expression)
 	if(!exp)
-		throw new Error(exp + " failed verify on ListOfMatchExpression:34");
+		throw new Error(exp + " failed verify on ListOfMatchExpression:add");
 	if(this._expressions) {
 		this._expressions.push(exp);
 	} else {
@@ -52,10 +43,54 @@ proto.add = function add( exp ){
  *
  */
 proto.clearAndRelease = function clearAndRelease(){
-	// File: expression_tree.h lines: 45-44
 	this._expressions = []; // empty the expressions
 };
 
+/**
+ *
+ * Get the length of the list
+ * @method numChildren
+ * @param
+ *
+ */
+proto.numChildren = function numChildren(){
+	return this._expressions.length;
+};
+
+/**
+ *
+ * Get an item from the expressions
+ * @method getChild
+ * @param i index of the child
+ *
+ */
+proto.getChild = function getChild(i){
+	return this._expressions[i];
+};
+
+/**
+ *
+ * Get the expressions
+ * @method getChildVector
+ * @param
+ *
+ */
+proto.getChildVector = function getChildVector(){
+	return this._expressions;
+};
+
+/**
+ *
+ * Print the debug info from each expression in the list
+ * @method _debugList
+ * @param level
+ *
+ */
+proto._debugList = function _debugList(debug, level){
+	for (var i = 0; i < this._expressions.length; i++ )
+		this._expressions[i].debugString(debug, level + 1);
+};
+
 /**
  *
  * Check if the input list is considered the same as this one
@@ -64,7 +99,6 @@ proto.clearAndRelease = function clearAndRelease(){
  *
  */
 proto.equivalent = function equivalent(other){
-	// File: expression_tree.cpp lines: 45-59
 	if (this._matchType != other._matchType)
 		return false;
 
@@ -80,27 +114,3 @@ proto.equivalent = function equivalent(other){
 
 	return true;
 };
-
-/**
- *
- * Get an item from the expressions
- * @method getChild
- * @param
- *
- */
-proto.getChild = function getChild(i){
-	// File: expression_tree.h lines: 48-47
-	return this._expressions[i];
-};
-
-/**
- *
- * Get the length of the list
- * @method numChildren
- * @param
- *
- */
-proto.numChildren = function numChildren(){
-	// File: expression_tree.h lines: 47-46
-	return this._expressions.length;
-};

+ 42 - 49
lib/pipeline/matcher/MatchDetails.js

@@ -1,42 +1,53 @@
 "use strict";
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * MatchDetails
+ * @class MatchDetails
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ **/
 var MatchDetails = module.exports = function (){
-	// File: match_details.cpp lines: 27-29
 	this._elemMatchKeyRequested = false;
 	this.resetOutput();
 }, klass = MatchDetails, base =  Object  , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// File: match_details.h lines: 60-60
 proto._elemMatchKey = undefined;
 
-// File: match_details.h lines: 59-59
 proto._elemMatchKeyRequested = undefined;
 
-// File: match_details.h lines: 58-58
 proto._loadedRecord = undefined;
 
 /**
  *
- * Return the _elemMatchKey property
- * @method elemMatchKey
+ * Set _loadedRecord to false and _elemMatchKey to undefined
+ * @method resetOutput
  *
  */
-proto.elemMatchKey = function elemMatchKey(){
-	// File: match_details.cpp lines: 41-43
-	if (!this.hasElemMatchKey()) throw new Error("no elem match key MatchDetails:29");
-	return this._elemMatchKey;
+proto.resetOutput = function resetOutput(){
+	this._loadedRecord = false;
+	this._elemMatchKey = undefined;
 };
 
 /**
  *
- * Return the _elemMatchKey property so we can check if exists
- * @method hasElemMatchKey
+ * Return a string representation of ourselves
+ * @method toString
  *
  */
-proto.hasElemMatchKey = function hasElemMatchKey(){
-	// File: match_details.cpp lines: 37-38
-	return this._elemMatchKey;
+proto.toString = function toString(){
+	return "loadedRecord: " + this._loadedRecord + " " + "elemMatchKeyRequested: " + this._elemMatchKeyRequested + " " + "elemMatchKey: " + ( this._elemMatchKey ? this._elemMatchKey : "NONE" ) + " ";
+};
+
+/**
+ *
+ * Set the _loadedRecord property
+ * @method setLoadedRecord
+ * @param loadedRecord
+ *
+ */
+proto.setLoadedRecord = function setLoadedRecord(loadedRecord){
+	this._loadedRecord = loadedRecord;
 };
 
 /**
@@ -46,7 +57,6 @@ proto.hasElemMatchKey = function hasElemMatchKey(){
  *
  */
 proto.hasLoadedRecord = function hasLoadedRecord(){
-	// File: match_details.h lines: 41-40
 	return this._loadedRecord;
 };
 
@@ -57,7 +67,6 @@ proto.hasLoadedRecord = function hasLoadedRecord(){
  *
  */
 proto.needRecord = function needRecord(){
-	// File: match_details.h lines: 45-44
 	return this._elemMatchKeyRequested;
 };
 
@@ -68,20 +77,28 @@ proto.needRecord = function needRecord(){
  *
  */
 proto.requestElemMatchKey = function requestElemMatchKey(){
-	// File: match_details.h lines: 50-49
 	this._elemMatchKeyRequested = true;
 };
 
 /**
  *
- * Set _loadedRecord to false and _elemMatchKey to undefined
- * @method resetOutput
+ * Return the _elemMatchKey property so we can check if exists
+ * @method hasElemMatchKey
  *
  */
-proto.resetOutput = function resetOutput(){
-	// File: match_details.cpp lines: 32-34
-	this._loadedRecord = false;
-	this._elemMatchKey = undefined;
+proto.hasElemMatchKey = function hasElemMatchKey(){
+	return (typeof this._elemMatchKey !== 'undefined');
+};
+
+/**
+ *
+ * Return the _elemMatchKey property
+ * @method elemMatchKey
+ *
+ */
+proto.elemMatchKey = function elemMatchKey(){
+	if (!this.hasElemMatchKey()) throw new Error("no elem match key MatchDetails:29");
+	return this._elemMatchKey;
 };
 
 /**
@@ -92,31 +109,7 @@ proto.resetOutput = function resetOutput(){
  *
  */
 proto.setElemMatchKey = function setElemMatchKey(elemMatchKey){
-	// File: match_details.cpp lines: 46-49
 	if ( this._elemMatchKeyRequested ) {
 		this._elemMatchKey = elemMatchKey;
 	}
 };
-
-/**
- *
- * Set the _loadedRecord property
- * @method setLoadedRecord
- * @param loadedRecord
- *
- */
-proto.setLoadedRecord = function setLoadedRecord(loadedRecord){
-	// File: match_details.h lines: 39-38
-	this._loadedRecord = loadedRecord;
-};
-
-/**
- *
- * Return a string representation of ourselves
- * @method toString
- *
- */
-proto.toString = function toString(){
-	// File: match_details.cpp lines: 52-57
-	return "loadedRecord: " + this._loadedRecord + " " + "elemMatchKeyRequested: " + this._elemMatchKeyRequested + " " + "elemMatchKey: " + ( this._elemMatchKey ? this._elemMatchKey : "NONE" ) + " ";
-};

+ 139 - 88
lib/pipeline/matcher/MatchExpression.js

@@ -1,172 +1,223 @@
 "use strict";
 
-// Autogenerated by cport.py on 2013-09-17 14:37
-var MatchExpression = module.exports = function MatchExpression( type ){
+/**
+ * Files: matcher/expression.h/cpp
+ * Function order follows that in the header file
+ * @class MatchExpression
+ * @namespace mungedb-aggregate.pipeline.matcher
+ * @module mungedb-aggregate
+ * @constructor
+ * @param type {String} The type of the match expression
+ */
+var MatchExpression = module.exports = function MatchExpression(type){
 	this._matchType = type;
-}, klass = MatchExpression, base =  Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = MatchExpression, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 // DEPENDENCIES
 var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes;
 
-	// File: expression.h lines: 172-172
-proto._matchType = undefined;
-
-// File: expression.h lines: 173-173
-proto._tagData = undefined;
+/**
+ * Return the _matchType property
+ * @method matchType
+ */
+proto.matchType = function matchType(){
+	return this._matchType;
+};
 
 /**
- *
- * Writes a debug string for this object
- * @method debugString
- * @param level
- *
+ * Return the number of children we have
+ * @method numChildren
  */
-proto._debugAddSpace = function _debugAddSpace(level){
-	// File: expression.cpp lines: 37-39
-	return new Array( level + 1).join("    ");
+proto.numChildren = function numChildren( ){
+	return 0;
 };
 
+
 /**
- *
- * Get our child elements
+ * Get the i-th child.
  * @method getChild
- *
  */
-proto.getChild = function getChild() {
-	// File: expression.h lines: 78-77
-	throw new Error('Virtual function called.');
+proto.getChild = function getChild(i) {
+	return null;
 };
 
+/**
+ * Get all the children of a node.
+ * @method getChild
+ */
+proto.getChildVector = function getChildVector(i) {
+	return null;
+};
 
 /**
+ * Get the path of the leaf.  Returns StringData() if there is
+ * no path (node is logical).
+ * @method path
+ */
+proto.path = function path( ){
+	return "";
+};
+
+/*
+ * Notes on structure:
+ * isLogical, isArray, and isLeaf define three partitions of all possible operators.
  *
- * Return the _tagData property
- * @method getTag
+ * isLogical can have children and its children can be arbitrary operators.
+ *
+ * isArray can have children and its children are predicates over one field.
  *
+ * isLeaf is a predicate over one field.
  */
-proto.getTag = function getTag(){
-	// File: expression.h lines: 159-158
-	return this._tagData;
+
+/**
+ * Is this node a logical operator?  All of these inherit from ListOfMatchExpression.
+ * AND, OR, NOT, NOR.
+ * @method isLogical
+ */
+proto.isLogical = function isLogical(){
+	switch( this._matchType ){
+		case "AND":
+		case "OR":
+		case "NOT":
+		case "NOR":
+			return true;
+		default:
+			return false;
+	}
+	return false;
 };
 
 /**
+ * Is this node an array operator?  Array operators have multiple clauses but operate on one
+ * field.
  *
- * Return if our _matchType needs an array
+ * ALL (AllElemMatchOp)
+ * ELEM_MATCH_VALUE, ELEM_MATCH_OBJECT, SIZE (ArrayMatchingMatchExpression)
  * @method isArray
- *
  */
 proto.isArray = function isArray(){
-	// File: expression.h lines: 111-113
 	switch (this._matchType){
-		case 'SIZE':
-		case 'ALL':
-		case 'ELEM_MATCH_VALUE':
-		case 'ELEM_MATCH_OBJECT':
+		case "SIZE":
+		case "ALL":
+		case "ELEM_MATCH_VALUE":
+		case "ELEM_MATCH_OBJECT":
 			return true;
 		default:
 			return false;
 	}
-
 	return false;
 };
 
 /**
+ * Not-internal nodes, predicates over one field.  Almost all of these inherit
+ * from LeafMatchExpression.
  *
- * Check if we do not need an array, and we are not a logical element (leaves are very emotional)
+ * Exceptions: WHERE, which doesn't have a field.
+ *             TYPE_OPERATOR, which inherits from MatchExpression due to unique
+ * 							array semantics.
  * @method isLeaf
- *
  */
 proto.isLeaf = function isLeaf(){
-	// File: expression.h lines: 124-125
 	return !this.isArray() && !this.isLogical();
 };
 
 /**
- *
- * Check if we are a vulcan
- * @method isLogical
- *
+ * XXX: document
+ * @method shallowClone
+ * @return {MatchExpression}
+ * @abstract
  */
-proto.isLogical = function isLogical(){
-	// File: expression.h lines: 100-101
-	switch( this._matchType ){
-		case 'AND':
-		case 'OR':
-		case 'NOT':
-		case 'NOR':
-			return true;
-		default:
-			return false;
-	}
-	return false;
+proto.shallowClone = function shallowClone() {
+	throw new Error("NOT IMPLEMENTED");
 };
 
 /**
- *
- * Return the _matchType property
- * @method matchType
- *
+ * XXX document
+ * @method equivalent
+ * @return {Boolean}
+ * @abstract
  */
-proto.matchType = function matchType(){
-	// File: expression.h lines: 67-66
-	return this._matchType;
+proto.equivalent = function equivalent() {
+	throw new Error("NOT IMPLEMENTED");
 };
 
+//
+// Determine if a document satisfies the tree-predicate.
+//
+
+/**
+ * @method matches
+ * @return {Boolean}
+ * @abstract
+ */
+proto.matches = function matches(doc, details/* = 0 */) {
+	throw new Error("NOT IMPLEMENTED");
+};
+
+
 /**
- *
  * Wrapper around matches function
- * @method matchesBSON
- * @param
- *
+ * @method matchesJSON
  */
-proto.matchesBSON = function matchesBSON(doc, details){
-	// File: expression.cpp lines: 42-44
+proto.matchesJSON = function matchesJSON(doc, details/* = 0 */){
 	return this.matches(doc, details);
 };
 
 /**
- *
- * Return the number of children we have
- * @method numChildren
- *
+ * Determines if the element satisfies the tree-predicate.
+ * Not valid for all expressions (e.g. $where); in those cases, returns false.
+ * @method matchesSingleElement
  */
-proto.numChildren = function numChildren( ){
-	// File: expression.h lines: 73-72
-	return 0;
+proto.matchesSingleElement = function matchesSingleElement(doc) {
+	throw new Error("NOT IMPLEMENTED");
 };
 
 /**
- *
- * Return our internal path
- * @method path
- *
+ * Return the _tagData property
+ * @method getTag
  */
-proto.path = function path( ){
-	// File: expression.h lines: 83-82
-	return '';
+proto.getTag = function getTag(){
+	return this._tagData;
 };
 
 /**
- *
  * Set the _tagData property
  * @method setTag
  * @param data
- *
  */
 proto.setTag = function setTag(data){
-	// File: expression.h lines: 158-157
 	this._tagData = data;
 };
 
+proto.resetTag = function resetTag() {
+	this.setTag(null);
+	for(var i=0; i<this.numChildren(); i++) {
+		this.getChild(i).resetTag();
+	}
+};
+
 /**
- *
  * Call the debugString method
  * @method toString
- *
  */
 proto.toString = function toString(){
-	// File: expression.cpp lines: 31-34
-	return this.debugString( 0 );
+	return this.debugString(0);
 };
+/**
+ * Debug information
+ * @method debugString
+ */
+proto.debugString = function debugString(level) {
+	throw new Error("NOT IMPLEMENTED");
+};
+/**
+ * @method _debugAddSpace
+ * @param level
+ */
+proto._debugAddSpace = function _debugAddSpace(level){
+	return new Array(level+1).join("    ");
+};
+
+
 

+ 117 - 52
lib/pipeline/matcher/MatchExpressionParser.js

@@ -1,6 +1,6 @@
 "use strict";
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+// File: expression_parser.cpp
 var MatchExpressionParser = module.exports = function (){
 
 }, klass = MatchExpressionParser, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
@@ -32,6 +32,11 @@ var errors = require("../../Errors.js"),
 	AllElemMatchOp = require("./AllElemMatchOp.js"),
 	AtomicMatchExpression = require("./AtomicMatchExpression.js");
 
+proto.expressionParserTextCallback = require('./TextMatchExpressionParser').expressionParserTextCallbackReal;
+
+// The maximum allowed depth of a query tree. Just to guard against stack overflow.
+var MAXIMUM_TREE_DEPTH = 100;
+
 /**
  *
  * Check if the input element is an expression
@@ -39,8 +44,7 @@ var errors = require("../../Errors.js"),
  * @param element
  *
  */
-proto._isExpressionDocument = function _isExpressionDocument(element){
-	// File: expression_parser.cpp lines: 340-355
+proto._isExpressionDocument = function _isExpressionDocument(element, allowIncompleteDBRef){
 	if (!(element instanceof Object))
 		return false;
 
@@ -51,26 +55,50 @@ proto._isExpressionDocument = function _isExpressionDocument(element){
 	if (name[0] != '$')
 		return false;
 
-	if ("$ref" == name)
+	if (this._isDBRefDocument(element, allowIncompleteDBRef))
 		return false;
 
 	return true;
 };
 
+proto._isDBRefDocument = function _isDBRefDocument(obj, allowIncompleteDBRef) {
+	var hasRef, hasID, hasDB = false;
+
+	var i, fieldName, element = null,
+		keys = Object.keys(obj), length = keys.length;
+	for (i = 0; i < length; i++) {
+		fieldName = keys[i];
+		element = obj[fieldName];
+		if (!hasRef && fieldName === '$ref')
+			hasRef = true;
+		else if (!hasID && fieldName === '$id')
+			hasID = true;
+		else if (!hasDB && fieldName === '$db')
+			hasDB = true;
+	}
+
+	return allowIncompleteDBRef && (hasRef || hasID || hasDB) || (hasRef && hasID);
+};
+
 /**
  *
  * Parse the input object into individual elements
  * @method _parse
  * @param obj
- * @param topLevel
+ * @param level
  *
  */
-proto._parse = function _parse(obj, topLevel){
-	// File: expression_parser.cpp lines: 217-319
+proto._parse = function _parse(obj, level){
+	if (level > MAXIMUM_TREE_DEPTH)
+		return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
+			MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(obj)};
+
 	var rest, temp, status, element, eq, real;
 	var root = new AndMatchExpression();
 	var objkeys = Object.keys(obj);
 	var currname, currval;
+	var topLevel = level === 0;
+	level++;
 	for (var i = 0; i < objkeys.length; i++) {
 		currname = objkeys[i];
 		currval = obj[currname];
@@ -82,7 +110,7 @@ proto._parse = function _parse(obj, topLevel){
 				if (!(currval instanceof Array))
 					return {code:ErrorCodes.BAD_VALUE, description:"$or needs an array"};
 				temp = new OrMatchExpression();
-				status = this._parseTreeList(currval, temp);
+				status = this._parseTreeList(currval, temp, level);
 				if (status.code != ErrorCodes.OK)
 					return status;
 				root.add(temp);
@@ -91,7 +119,7 @@ proto._parse = function _parse(obj, topLevel){
 				if (!(currval instanceof Array))
 					return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
 				temp = new AndMatchExpression();
-				status = this._parseTreeList(currval, temp);
+				status = this._parseTreeList(currval, temp, level);
 				if (status.code != ErrorCodes.OK)
 					return status;
 				root.add(temp);
@@ -100,7 +128,7 @@ proto._parse = function _parse(obj, topLevel){
 				if (!(currval instanceof Array))
 					return {code:ErrorCodes.BAD_VALUE, description:"and needs an array"};
 				temp = new NorMatchExpression();
-				status = this._parseTreeList(currval, temp);
+				status = this._parseTreeList(currval, temp, level);
 				if (status.code != ErrorCodes.OK)
 					return status;
 				root.add(temp);
@@ -123,10 +151,14 @@ proto._parse = function _parse(obj, topLevel){
 				if (status.code != ErrorCodes.OK)
 					return status;
 				root.add(status.result);*/
+			} else if ('text' === rest) {
+				if (typeof currval !== 'object') {
+					return {code: ErrorCodes.BAD_VALUE, description: '$text expects an object'};
+				}
+
+				return this.expressionTextCallback(currval);
 			}
-			else if ("comment" == rest) {
-				1+1;
-			}
+			else if ("comment" == rest) {}
 			else {
 				return {code:ErrorCodes.BAD_VALUE, description:"unknown top level operator: " + currname};
 			}
@@ -135,7 +167,7 @@ proto._parse = function _parse(obj, topLevel){
 		}
 
 		if (this._isExpressionDocument(currval)) {
-			status = this._parseSub(currname, currval, root);
+			status = this._parseSub(currname, currval, root, level);
 			if (status.code != ErrorCodes.OK)
 				return status;
 			continue;
@@ -172,8 +204,7 @@ proto._parse = function _parse(obj, topLevel){
  * @param element
  *
  */
-proto._parseAll = function _parseAll(name, element){
-	// File: expression_parser.cpp lines: 512-583
+proto._parseAll = function _parseAll(name, element, level){
 	var status, i;
 
 	if (!(element instanceof Array))
@@ -201,7 +232,7 @@ proto._parseAll = function _parseAll(name, element){
 				return {code:ErrorCodes.BAD_VALUE, description:"$all/$elemMatch has to be consistent"};
 			}
 
-			status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch ); // TODO: wrong way to do this?
+			status = this._parseElemMatch("", hopefullyElemMatchElement.$elemMatch, level);
 			if (status.code != ErrorCodes.OK)
 				return status;
 			temp.add(status.result);
@@ -249,11 +280,14 @@ proto._parseAll = function _parseAll(name, element){
  *
  */
 proto._parseArrayFilterEntries = function _parseArrayFilterEntries(entries, theArray){
-	// File: expression_parser.cpp lines: 445-468
 	var status, e, r;
 	for (var i = 0; i < theArray.length; i++) {
 		e = theArray[i];
 
+		if (this._isExpressionDocument(e, false)) {
+			return {code:ErrorCodes.BAD_VALUE, description:"cannot nest $ under $in"};
+		}
+
 		if (e instanceof RegExp ) {
 			r = new RegexMatchExpression();
 			status = r.init("", e);
@@ -282,7 +316,6 @@ proto._parseArrayFilterEntries = function _parseArrayFilterEntries(entries, theA
  *
  */
 proto._parseComparison = function _parseComparison(name, cmp, element){
-	// File: expression_parser.cpp lines: 34-43
 	var temp = new ComparisonMatchExpression(cmp);
 
 	var status = temp.init(name, element);
@@ -300,17 +333,31 @@ proto._parseComparison = function _parseComparison(name, cmp, element){
  * @param element
  *
  */
-proto._parseElemMatch = function _parseElemMatch(name, element){
-	// File: expression_parser.cpp lines: 471-509
+proto._parseElemMatch = function _parseElemMatch(name, element, level){
 	var temp, status;
 	if (!(element instanceof Object))
 		return {code:ErrorCodes.BAD_VALUE, description:"$elemMatch needs an Object"};
 
-	if (this._isExpressionDocument(element)) {
+	// $elemMatch value case applies when the children all
+	// work on the field 'name'.
+	// This is the case when:
+	//     1) the argument is an expression document; and
+	//     2) expression is not a AND/NOR/OR logical operator. Children of
+	//        these logical operators are initialized with field names.
+	//     3) expression is not a WHERE operator. WHERE works on objects instead
+	//        of specific field.
+	var elt = element[Object.keys(element)[0]],
+		isElemMatchValue = this._isExpressionDocument(element, true) &&
+			elt !== '$and' &&
+			elt !== '$nor' &&
+			elt !== '$or' &&
+			elt !== '$where';
+
+	if (isElemMatchValue) {
 		// value case
 
 		var theAnd = new AndMatchExpression();
-		status = this._parseSub("", element, theAnd);
+		status = this._parseSub("", element, theAnd, level);
 		if (status.code != ErrorCodes.OK)
 			return status;
 
@@ -327,9 +374,13 @@ proto._parseElemMatch = function _parseElemMatch(name, element){
 		return {code:ErrorCodes.OK, result:temp};
 	}
 
+	// DBRef value case
+	// A DBRef document under a $elemMatch should be treated as an object case
+	// because it may contain non-DBRef fields in addition to $ref, $id and $db.
+
 	// object case
 
-	status = this._parse(element, false);
+	status = this._parse(element, level);
 	if (status.code != ErrorCodes.OK)
 		return status;
 
@@ -350,7 +401,6 @@ proto._parseElemMatch = function _parseElemMatch(name, element){
  *
  */
 proto._parseMOD = function _parseMOD(name, element){
-	// File: expression_parser.cpp lines: 360-387
 	var d,r;
 	if (!(element instanceof Array))
 		return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, needs to be an array"};
@@ -358,13 +408,13 @@ proto._parseMOD = function _parseMOD(name, element){
 		return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, not enough elements"};
 	if (element.length > 2)
 		return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, too many elements"};
-	if ((typeof(element[0]) != 'number')) {
+	if (typeof element[0] !== 'number') {
 		return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, divisor not a number"};
 	} else {
 		d = element[0];
 	}
-	if (( typeof(element[1]) != 'number') ) {
-		r = 0;
+	if (typeof element[1] !== 'number') {
+		return {code:ErrorCodes.BAD_VALUE, result:"malformed mod, remainder not a number"};
 	} else {
 		r = element[1];
 	}
@@ -385,8 +435,7 @@ proto._parseMOD = function _parseMOD(name, element){
  * @param element
  *
  */
-proto._parseNot = function _parseNot(name, element){
-	// File: expression_parser_tree.cpp lines: 55-91
+proto._parseNot = function _parseNot(name, element, level){
 	var status;
 	if (element instanceof RegExp) {
 		status = this._parseRegexElement(name, element);
@@ -406,7 +455,7 @@ proto._parseNot = function _parseNot(name, element){
 		return {code:ErrorCodes.BAD_VALUE, result:"$not cannot be empty"};
 
 	var theAnd = new AndMatchExpression();
-	status = this._parseSub(name, element, theAnd);
+	status = this._parseSub(name, element, theAnd, level);
 	if (status.code != ErrorCodes.OK)
 		return status;
 
@@ -434,7 +483,6 @@ proto._parseNot = function _parseNot(name, element){
  *
  */
 proto._parseRegexDocument = function _parseRegexDocument(name, doc){
-	// File: expression_parser.cpp lines: 402-442
 	var regex = '', regexOptions = '', e;
 
 	if(doc.$regex) {
@@ -450,7 +498,7 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
 			}
 			regex = (flagIndex? str : str.substr(1, flagIndex-1));
 			regexOptions = str.substr(flagIndex, str.length);
-		} else if (typeof(e) == 'string') {
+		} else if (typeof e  === 'string') {
 			regex = e;
 		} else {
 			return {code:ErrorCodes.BAD_VALUE, description:"$regex has to be a string"};
@@ -459,7 +507,7 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
 
 	if(doc.$options) {
 		e = doc.$options;
-		if(typeof(e) == 'string') {
+		if(typeof(e) === 'string') {
 			regexOptions = e;
 		} else {
 			return {code:ErrorCodes.BAD_VALUE, description:"$options has to be a string"};
@@ -484,7 +532,6 @@ proto._parseRegexDocument = function _parseRegexDocument(name, doc){
  *
  */
 proto._parseRegexElement = function _parseRegexElement(name, element){
-	// File: expression_parser.cpp lines: 390-399
 	if (!(element instanceof RegExp))
 		return {code:ErrorCodes.BAD_VALUE, description:"not a regex"};
 
@@ -516,18 +563,26 @@ proto._parseRegexElement = function _parseRegexElement(name, element){
  * @param root
  *
  */
-proto._parseSub = function _parseSub(name, sub, root){
-	// File: expression_parser.cpp lines: 322-337
+proto._parseSub = function _parseSub(name, sub, root, level){
 	var subkeys = Object.keys(sub),
 		currname, currval;
 
+	if (level > MAXIMUM_TREE_DEPTH) {
+		return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
+			MAXIMUM_TREE_DEPTH + " at " + JSON.stringify(sub)};
+	}
+
+	level++;
+
+	// DERIVATION: We are not implementing Geo functions yet.
+
 	for (var i = 0; i < subkeys.length; i++) {
 		currname = subkeys[i];
 		currval = sub[currname];
 		var deep = {};
 		deep[currname] = currval;
 
-		var status = this._parseSubField(sub, root, name, deep);
+		var status = this._parseSubField(sub, root, name, deep, level);
 		if (status.code != ErrorCodes.OK)
 			return status;
 
@@ -548,20 +603,19 @@ proto._parseSub = function _parseSub(name, sub, root){
  * @param element
  *
  */
-proto._parseSubField = function _parseSubField(context, andSoFar, name, element){
-	// File: expression_parser.cpp lines: 46-214
+proto._parseSubField = function _parseSubField(context, andSoFar, name, element, level){
 	// TODO: these should move to getGtLtOp, or its replacement
 	var currname = Object.keys(element)[0];
 	var currval = element[currname];
 	if ("$eq" == currname)
 		return this._parseComparison(name, 'EQ', currval);
 
-	if ("$not" == currname) {
-		return this._parseNot(name, currval);
-	}
+	if ("$not" == currname)
+		return this._parseNot(name, currval, level);
 
 	var status, temp, temp2;
 	switch (currname) {
+		// TODO: -1 is apparently a value for mongo, but we handle strings so...
 	case '$lt':
 		return this._parseComparison(name, 'LT', currval);
 	case '$lte':
@@ -571,6 +625,11 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
 	case '$gte':
 		return this._parseComparison(name, 'GTE', currval);
 	case '$ne':
+		// Just because $ne can be rewritten as the negation of an
+		// equality does not mean that $ne of a regex is allowed. See SERVER-1705.
+		if (currval instanceof RegExp) {
+			return {code:ErrorCodes.BAD_VALUE, description:"Can't have regex as arg to $ne."};
+		}
 		status = this._parseComparison(name, 'EQ', currval);
 		if (status.code != ErrorCodes.OK)
 			return status;
@@ -618,11 +677,19 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
 			// matching old odd semantics
 			size = 0;
 		else if (typeof(currval) === 'number')
-			size = currval;
+			// SERVER-11952. Setting 'size' to -1 means that no documents
+			// should match this $size expression.
+			if (currval < 0)
+				size = -1;
+			else
+				size = currval;
 		else {
 			return {code:ErrorCodes.BAD_VALUE, description:"$size needs a number"};
 		}
 
+		// DERIVATION/Potential bug: Mongo checks to see if doube values are exactly equal to
+		// their int converted version. If not, size = -1.
+
 		temp = new SizeMatchExpression();
 		status = temp.init(name, size);
 		if (status.code != ErrorCodes.OK)
@@ -637,7 +704,7 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
 		status = temp.init(name);
 		if (status.code != ErrorCodes.OK)
 			return status;
-		if (currval)
+		if (currval) // DERIVATION: This may have to check better than truthy? Need to look at TrueValue
 			return {code:ErrorCodes.OK, result:temp};
 		temp2 = new NotMatchExpression();
 		status = temp2.init(temp);
@@ -674,10 +741,10 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
 		return this._parseRegexDocument(name, context);
 
 	case '$elemMatch':
-		return this._parseElemMatch(name, currval);
+		return this._parseElemMatch(name, currval, level);
 
 	case '$all':
-		return this._parseAll(name, currval);
+		return this._parseAll(name, currval, level);
 
 	case '$geoWithin':
 	case '$geoIntersects':
@@ -701,8 +768,7 @@ proto._parseSubField = function _parseSubField(context, andSoFar, name, element)
  * @param out
  *
  */
-proto._parseTreeList = function _parseTreeList(arr, out){
-	// File: expression_parser_tree.cpp lines: 33-52
+proto._parseTreeList = function _parseTreeList(arr, out, level){
 	if (arr.length === 0)
 		return {code:ErrorCodes.BAD_VALUE, description:"$and/$or/$nor must be a nonempty array"};
 
@@ -713,7 +779,7 @@ proto._parseTreeList = function _parseTreeList(arr, out){
 		if (!(element instanceof Object))
 			return {code:ErrorCodes.BAD_VALUE, description:"$or/$and/$nor entries need to be full objects"};
 
-		status = this._parse(element, false);
+		status = this._parse(element, level);
 		if (status.code != ErrorCodes.OK)
 			return status;
 
@@ -730,6 +796,5 @@ proto._parseTreeList = function _parseTreeList(arr, out){
  *
  */
 proto.parse = function parse(obj){
-	// File: expression_parser.h lines: 40-41
-	return this._parse(obj, true);
+	return this._parse(obj, 0);
 };

+ 14 - 279
lib/pipeline/matcher/Matcher2.js

@@ -1,8 +1,13 @@
 "use strict";
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+/**
+ * Matcher2 is a simple wrapper around a JSONObj and the MatchExpression created from it.
+ * @class Matcher2
+ * @namespace mungedb-aggregate.pipeline.matcher2
+ * @module mungedb-aggregate
+ * @constructor
+ */
 var Matcher2 = module.exports = function Matcher2(pattern, nested){
-	// File: matcher.cpp lines: 83-92
 	this._pattern = pattern;
 	this.parser = new MatchExpressionParser();
 	var result = this.parser.parse(pattern);
@@ -14,243 +19,12 @@ var Matcher2 = module.exports = function Matcher2(pattern, nested){
 // DEPENDENCIES
 var errors = require("../../Errors.js"),
 	ErrorCodes = errors.ErrorCodes,
-	MatchExpression = require("./MatchExpression.js"),
-	MatchExpressionParser = require("./MatchExpressionParser.js"),
-	FalseMatchExpression = require("./FalseMatchExpression.js"),
-	ComparisonMatchExpression = require("./ComparisonMatchExpression.js"),
-	InMatchExpression = require("./InMatchExpression.js"),
-	AndMatchExpression = require("./AndMatchExpression.js"),
-	OrMatchExpression = require("./OrMatchExpression.js"),
-	IndexKeyMatchableDocument = require('./IndexKeyMatchableDocument.js'),
-	ListOfMatchExpression = require('./ListOfMatchExpression.js'),
-	LeafMatchExpression = require("./LeafMatchExpression.js");
+	MatchExpressionParser = require("./MatchExpressionParser.js");
 
-// File: matcher.h lines: 82-82
+//PROTOTYPE MEMBERS
 proto._expression = undefined;
-
-// File: matcher.h lines: 80-80
-proto._indexKey = undefined;
-
-// File: matcher.h lines: 79-79
 proto._pattern = undefined;
 
-// File: matcher.h lines: 84-84
-proto._spliceInfo = undefined;
-
-/**
- *
- * Figure out where our index is
- * @method _spliceForIndex
- * @param keys
- * @param full
- * @param spliceInfo
- *
- */
-proto._spliceForIndex = function _spliceForIndex(keys, full, spliceInfo){
-	// File: matcher.cpp lines: 236-380
-	var dup, i, obj, lme;
-	switch (full) {
-		case MatchExpression.ALWAYS_FALSE:
-			return new FalseMatchExpression();
-
-		case MatchExpression.GEO_NEAR:
-		case MatchExpression.NOT:
-		case MatchExpression.NOR:
-			// maybe?
-			return null;
-
-		case MatchExpression.OR:
-
-		case MatchExpression.AND:
-			dup = new ListOfMatchExpression();
-			for (i = 0; i < full.numChildren(); i++) {
-				var sub = this._spliceForIndex(keys, full.getChild(i), spliceInfo);
-				if (!sub)
-					continue;
-				if (!dup.get()) {
-					if (full.matchType() == MatchExpression.AND)
-						dup.reset(new AndMatchExpression());
-					else
-						dup.reset(new OrMatchExpression());
-				}
-				dup.add(sub);
-			}
-			if (dup.get()) {
-				if (full.matchType() == MatchExpression.OR &&  dup.numChildren() != full.numChildren()) {
-					// TODO: I think this should actuall get a list of all the fields
-					// and make sure that's the same
-					// with an $or, have to make sure its all or nothing
-					return null;
-				}
-				return dup.release();
-			}
-			return null;
-
-		case MatchExpression.EQ:
-			var cmp = new ComparisonMatchExpression(full);
-
-			if (cmp.getRHS().type() == Array) {
-				// need to convert array to an $in
-
-				if (!keys.count(cmp.path().toString()))
-					return null;
-
-				var newIn = new InMatchExpression();
-				newIn.init(cmp.path());
-
-				if (newIn.getArrayFilterEntries().addEquality(cmp.getRHS()).isOK())
-					return null;
-
-				if (cmp.getRHS().Obj().isEmpty())
-					newIn.getArrayFilterEntries().addEquality(undefined);
-
-				obj = cmp.getRHS().Obj();
-				for(i in obj) {
-					var s = newIn.getArrayFilterEntries().addEquality( obj[i].next() );
-					if (s.code != ErrorCodes.OK)
-						return null;
-				}
-
-				return newIn.release();
-			}
-			else if (cmp.getRHS().type() === null) {
-				//spliceInfo.hasNullEquality = true;
-				return null;
-			}
-			break;
-
-		case MatchExpression.LTE:
-		case MatchExpression.LT:
-		case MatchExpression.GT:
-		case MatchExpression.GTE:
-			cmp = new ComparisonMatchExpression(full);
-
-			if ( cmp.getRHS().type() === null) {
-				// null and indexes don't play nice
-				//spliceInfo.hasNullEquality = true;
-				return null;
-			}
-			break;
-
-		case MatchExpression.REGEX:
-		case MatchExpression.MOD:
-			lme = new LeafMatchExpression(full);
-			if (!keys.count(lme.path().toString()))
-				return null;
-			return lme.shallowClone();
-
-		case MatchExpression.MATCH_IN:
-			lme = new LeafMatchExpression(full);
-			if (!keys.count(lme.path().toString()))
-				return null;
-			var cloned = new InMatchExpression(lme.shallowClone());
-			if (cloned.getArrayFilterEntries().hasEmptyArray())
-				cloned.getArrayFilterEntries().addEquality(undefined);
-
-			// since { $in : [[1]] } matches [1], need to explode
-			for (i = cloned.getArrayFilterEntries().equalities().begin(); i != cloned.getArrayFilterEntries().equalities().end(); ++i) {
-				var x = cloned[i];
-				if (x.type() == Array) {
-					for(var j in x) {
-						cloned.getArrayFilterEntries().addEquality(x[j]);
-					}
-				}
-			}
-
-			return cloned;
-
-		case MatchExpression.ALL:
-			// TODO: convert to $in
-			return null;
-
-		case MatchExpression.ELEM_MATCH_OBJECT:
-		case MatchExpression.ELEM_MATCH_VALUE:
-			// future
-			return null;
-
-		case MatchExpression.GEO:
-		case MatchExpression.SIZE:
-		case MatchExpression.EXISTS:
-		case MatchExpression.NIN:
-		case MatchExpression.TYPE_OPERATOR:
-		case MatchExpression.ATOMIC:
-		case MatchExpression.WHERE:
-			// no go
-			return null;
-	}
-
-	return null;
-};
-
-/**
- *
- * return if our _expression property is atomic or not
- * @method atomic
- *
- */
-proto.atomic = function atomic(){
-	// File: matcher.cpp lines: 120-133
-	if (!this._expression)
-		return false;
-
-	if (this._expression.matchType() == MatchExpression.ATOMIC)
-		return true;
-
-	// we only go down one level
-	for (var i = 0; i < this._expression.numChildren(); i++) {
-		if (this._expression.getChild(i).matchType() == MatchExpression.ATOMIC)
-			return true;
-	}
-
-	return false;
-};
-
-/**
- *
- * Return the _pattern property
- * @method getQuery
- *
- */
-proto.getQuery = function getQuery(){
-	// File: matcher.h lines: 65-64
-	return this._pattern;
-};
-
-/**
- *
- * Check if we exist
- * @method hasExistsFalse
- *
- */
-proto.hasExistsFalse = function hasExistsFalse(){
-	// File: matcher.cpp lines: 172-180
-	if (this._spliceInfo.hasNullEquality) {
-		// { a : NULL } is very dangerous as it may not got indexed in some cases
-		// so we just totally ignore
-		return true;
-	}
-
-	return this._isExistsFalse(this._expression.get(), false, this._expression.matchType() == MatchExpression.AND ? -1 : 0);
-};
-
-/**
- *
- * Find if we have a matching key inside us
- * @method keyMatch
- * @param docMatcher
- *
- */
-proto.keyMatch = function keyMatch(docMatcher){
-	// File: matcher.cpp lines: 199-206
-	if (!this._expression)
-		return docMatcher._expression.get() === null;
-	if (!docMatcher._expression)
-		return false;
-	if (this._spliceInfo.hasNullEquality)
-		return false;
-	return this._expression.equivalent(docMatcher._expression.get());
-};
-
 /**
  *
  * matches checks the input doc against the internal element path to see if it is a match
@@ -260,58 +34,20 @@ proto.keyMatch = function keyMatch(docMatcher){
  *
  */
 proto.matches = function matches(doc, details){
-	// File: matcher.cpp lines: 105-116
 	if (!this._expression)
 		return true;
 
-	if (this._indexKey == {})
-		return this._expression.matchesBSON(doc, details);
-
-	if ((doc != {}) && (Object.keys(doc)[0]))
-		return this._expression.matchesBSON(doc, details);
-
-	var mydoc = new IndexKeyMatchableDocument(this._indexKey, doc);
-	return this._expression.matches(mydoc, details);
+	return this._expression.matchesJSON(doc, details);
 };
 
 /**
  *
- * Check if we are a simple match
- * @method singleSimpleCriterion
- *
- */
-proto.singleSimpleCriterion = function singleSimpleCriterion(){
-	// File: matcher.cpp lines: 184-196
-	if (!this._expression)
-		return false;
-
-	if (this._expression.matchType() == MatchExpression.EQ)
-		return true;
-
-	if (this._expression.matchType() == MatchExpression.AND && this._expression.numChildren() == 1 && this._expression.getChild(0).matchType() == MatchExpression.EQ)
-		return true;
-
-	return false;
-};
-
-/**
- *
- * Wrapper around _spliceForIndex
- * @method spliceForIndex
- * @param key
- * @param full
- * @param spliceInfo
+ * Return the _pattern property
+ * @method getQuery
  *
  */
-proto.spliceForIndex = function spliceForIndex(key, full, spliceInfo){
-	// File: matcher.cpp lines: 209-217
-	var keys = [],
-		e, i;
-	for (i in key) {
-		e = key[i];
-		keys.insert(e.fieldName());
-	}
-	return this._spliceForIndex(keys, full, spliceInfo);
+proto.getQuery = function getQuery(){
+	return this._pattern;
 };
 
 /**
@@ -321,6 +57,5 @@ proto.spliceForIndex = function spliceForIndex(key, full, spliceInfo){
  *
  */
 proto.toString = function toString(){
-	// File: matcher.h lines: 66-65
 	return this._pattern.toString();
 };

+ 10 - 16
lib/pipeline/matcher/ModMatchExpression.js

@@ -1,17 +1,16 @@
 "use strict";
-var LeafMatchExpression = require('./LeafMatchExpression');
+var LeafMatchExpression = require('./LeafMatchExpression'),
+	ErrorCodes = require("../../Errors.js").ErrorCodes;
 
-// Autogenerated by cport.py on 2013-09-17 14:37
+// File: expression_leaf.h
 var ModMatchExpression = module.exports = function ModMatchExpression(){
 	base.call(this);
 	this._matchType = 'MOD';
 }, klass = ModMatchExpression, base =  LeafMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-// File: expression_leaf.h lines: 210-210
 proto._divisor = undefined;
 
-// File: expression_leaf.h lines: 211-211
 proto._remainder = undefined;
 
 /**
@@ -22,7 +21,6 @@ proto._remainder = undefined;
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 253-261
 	return this._debugAddSpace( level ) + this.path() + " mod " + this._divisor + " % x == " + this._remainder + (this.getTag() ? " " + this.getTag().debugString() : '') + "\n";
 };
 
@@ -34,10 +32,9 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 264-272
-	if(other._matchType != 'MOD')
+	if(other._matchType !== this._matchType)
 		return false;
-	return this.path() == other.path() && this._divisor == other._divisor && this._remainder == other._remainder;
+	return this.path() === other.path() && this._divisor === other._divisor && this._remainder === other._remainder;
 };
 
 /**
@@ -47,7 +44,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.getDivisor = function getDivisor(){
-	// File: expression_leaf.h lines: 206-205
 	return this._divisor;
 };
 
@@ -58,7 +54,6 @@ proto.getDivisor = function getDivisor(){
  *
  */
 proto.getRemainder = function getRemainder( /*  */ ){
-	// File: expression_leaf.h lines: 207-206
 	return this._remainder;
 };
 
@@ -71,9 +66,8 @@ proto.getRemainder = function getRemainder( /*  */ ){
  *
  */
 proto.init = function init(path,divisor,remainder) {
-	// File: expression_leaf.cpp lines: 239-244
 	if (divisor === 0 ){
-		return {'code':'BAD_VALUE', 'desc':'Divisor cannot be 0'};
+		return {'code':ErrorCodes.BAD_VALUE, 'desc':'Divisor cannot be 0'};
 	}
 
 	this._divisor = divisor;
@@ -89,12 +83,11 @@ proto.init = function init(path,divisor,remainder) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression_leaf.cpp lines: 247-250
-	if(typeof(e) != 'number') {
+	if(typeof(e) !== 'number') {
 		return false;
 	}
 
-	return (e % this._divisor) == this._remainder;
+	return (e % this._divisor) === this._remainder;
 };
 
 /**
@@ -104,9 +97,10 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression_leaf.h lines: 194-197
 	var e = new ModMatchExpression();
 	e.init(this.path(),this._divisor, this._remainder);
+	if (this.getTag())
+		e.setTag(this.getTag().clone());
 	return e;
 };
 

+ 5 - 0
lib/pipeline/matcher/NorMatchExpression.js

@@ -73,6 +73,11 @@ proto.shallowClone = function shallowClone(){
 	for (var i = 0; i < this.numChildren(); i++) {
 		e.add(this.getChild(i).shallowClone());
 	}
+
+	if (this.getTag()) {
+		e.setTag(this.getTag().clone());
+	}
+
 	return e;
 };
 

+ 9 - 16
lib/pipeline/matcher/NotMatchExpression.js

@@ -1,13 +1,10 @@
 "use strict";
 var MatchExpression = require('./MatchExpression');
-
-	// Autogenerated by cport.py on 2013-09-17 14:37
 var NotMatchExpression = module.exports = function NotMatchExpression(){
 	base.call(this);
 	this._matchType = 'NOT';
 }, klass = NotMatchExpression, base =  Object  , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-	// File: expression_tree.h lines: 152-152
 proto._exp = undefined;
 
 /**
@@ -18,7 +15,6 @@ proto._exp = undefined;
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_tree.cpp lines: 146-149
 	return this._debugAddSpace( level ) + "$not\n" + this._exp._debugString( level + 1 );
 };
 
@@ -30,19 +26,17 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_tree.cpp lines: 152-156
 	return other._matchType == 'NOT' && this._exp.equivalent(other.getChild(0));
 };
 
 /**
  *
- * Return the _exp property
- * @method getChild
+ * Return the reset child
+ * @method resetChild
  *
  */
-proto.getChild = function getChild() {
-	// File: expression_tree.h lines: 148-147
-	return this._exp;
+proto.resetChild = function resetChild(newChild) {
+	this._exp.reset(newChild);
 };
 
 /**
@@ -53,7 +47,6 @@ proto.getChild = function getChild() {
  *
  */
 proto.init = function init(exp) {
-	// File: expression_tree.h lines: 123-125
 	this._exp = exp;
 	return {'code':'OK'};
 };
@@ -66,9 +59,8 @@ proto.init = function init(exp) {
  * @param details
  *
  */
-proto.matches = function matches(doc,details) {
-	// File: expression_tree.h lines: 135-136
-	return ! this._exp.matches( doc,null );
+proto.matches = function matches(doc, details) {
+	return ! this._exp.matches(doc, null);
 };
 
 /**
@@ -79,7 +71,6 @@ proto.matches = function matches(doc,details) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression_tree.h lines: 139-140
 	return ! this._exp.matchesSingleElement( e );
 };
 
@@ -91,7 +82,6 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.numChildren = function numChildren(){
-	// File: expression_tree.h lines: 147-146
 	return 1;
 };
 
@@ -105,6 +95,9 @@ proto.shallowClone = function shallowClone(){
 	// File: expression_tree.h lines: 128-132
 	var e = new NotMatchExpression();
 	e.init(this._exp.shallowClone());
+	if ( this.getTag() ) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
 

+ 6 - 0
lib/pipeline/matcher/OrMatchExpression.js

@@ -64,9 +64,15 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
 proto.shallowClone = function shallowClone(){
 	// File: expression_tree.h lines: 86-91
 	var clone = new OrMatchExpression();
+
 	for (var i = 0; i < this.numChildren(); i++) {
 		clone.add(this.getChild(i).shallowClone());
 	}
+
+	if (this.getTag()) {
+		clone.setTag(this.getTag().clone());
+	}
+
 	return clone;
 };
 

+ 42 - 25
lib/pipeline/matcher/RegexMatchExpression.js

@@ -1,23 +1,17 @@
 "use strict";
-var LeafMatchExpression = require('./LeafMatchExpression');
+var XRegExp = require('xregexp').XRegExp,
+	LeafMatchExpression = require('./LeafMatchExpression'),
+	ErrorCodes = require('../../Errors').ErrorCodes;
 
 
-	// Autogenerated by cport.py on 2013-09-17 14:37
 var RegexMatchExpression = module.exports = function RegexMatchExpression(){
-	base.call(this);
-	this._matchType = 'REGEX';
+	base.call(this, 'REGEX');
 }, klass = RegexMatchExpression, base =  LeafMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-	// File: expression_leaf.h lines: 160-160
 klass.MaxPatternSize = 32764;
 
-	// File: expression_leaf.h lines: 184-184
 proto._flags = undefined;
-
-	// File: expression_leaf.h lines: 185-185
 proto._re = undefined;
-
-	// File: expression_leaf.h lines: 183-183
 proto._regex = undefined;
 
 /**
@@ -28,8 +22,19 @@ proto._regex = undefined;
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 225-234
-	return this._debugAddSpace( level ) + this.path() + " regex /" + this._regex + "/" + this._flags + (this.getTag() ? ' ' + this.getTag().debugString : '') + "\n";
+	var debug = this._debugAddSpace( level );
+	debug += this.path() + " regex /" + this._regex + "/" + this._flags;
+
+	var td = this.getTag();
+	if (td === null) {
+		debug += " " + td.debugString();
+	}
+	debug += "\n";
+	return debug;
+};
+
+proto.shortDebugString = function shortDebugString() {
+	return "/" + this._regex + "/" + this._flags;
 };
 
 /**
@@ -40,8 +45,9 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 177-185
-	return other._matchType == 'REGEX' && this.path() == other.path() && this._regex == other._regex && this._flags == other._flags;
+	if (this.matchType() !== other.matchType()) return false;
+	
+	return this.path() === other.path() && this._regex === other._regex && this._flags === other._flags;
 };
 
 /**
@@ -51,7 +57,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.getFlags = function getFlags(){
-	// File: expression_leaf.h lines: 180-179
 	return this._flags;
 };
 
@@ -63,7 +68,6 @@ proto.getFlags = function getFlags(){
  *
  */
 proto.getString = function getString(){
-	// File: expression_leaf.h lines: 179-178
 	return this._regex;
 };
 
@@ -76,14 +80,23 @@ proto.getString = function getString(){
  *
  */
 proto.init = function init(path,regex,flags) {
-	// File: expression_leaf.cpp lines: 196-205
 	if(regex.toString().length > klass.MaxPatternSize){
-		return {'code':'BAD_VALUE', 'desc':'Regular Expression too long.'};
+		return {'code':ErrorCodes.BAD_VALUE, 'desc':'Regular Expression too long.'};
+	}
+
+	if (regex instanceof RegExp){
+		this._regex = regex.source;
+		this._re = regex;
+		this._flags = (this._re.ignoreCase ? 'i' : '') + (this._re.multiline ? 'm' : '');
+	} else if (typeof regex === 'string' && (!flags || typeof flags === 'string' )) {
+		this._regex = regex;
+		//remove invalid flags, sort and uniquify them
+		this._flags = (flags || '').replace( /[^imxs]/g, '').split('').sort().filter(function(el,i,a){return i===a.indexOf(el);}).join('');
+		this._re = new XRegExp(regex,this._flags);
+	} else {
+		return {'code':ErrorCodes.BAD_VALUE, 'desc':'regex not a regex'};
 	}
 
-	this._regex = regex;
-	this._flags = flags;
-	this._re = new RegExp(regex,flags);
 	return this.initPath( path );
 };
 
@@ -96,11 +109,12 @@ proto.init = function init(path,regex,flags) {
  */
 
 proto.matchesSingleElement = function matchesSingleElement(e) {
-// File: expression_leaf.cpp lines: 208-222
 	if(e instanceof RegExp){
 		return e.toString() === this._re.toString();
 	}
-	return e && (e.match) && e.match(this._re);
+	if(typeof e === 'string'){
+		return this._re.test(e);
+	}
 	// No support for SYMBOLS currently
 };
 
@@ -111,9 +125,12 @@ proto.matchesSingleElement = function matchesSingleElement(e) {
  *
  */
 proto.shallowClone = function shallowClone(){
-	// File: expression_leaf.h lines: 167-170
 	var e = new RegexMatchExpression();
 	e.init( this.path(), this._regex, this._flags );
+
+	if ( this.getTag() ) {
+		e.setTag(this.getTag().clone());
+	}
+
 	return e;
 };
-

+ 14 - 17
lib/pipeline/matcher/SizeMatchExpression.js

@@ -1,14 +1,10 @@
 "use strict";
 var ArrayMatchingMatchExpression = require('./ArrayMatchingMatchExpression');
 
-
-// Autogenerated by cport.py on 2013-09-17 14:37
 var SizeMatchExpression = module.exports = function SizeMatchExpression(){
-	base.call(this);
-	this._matchType = 'SIZE';
+	base.call(this, 'SIZE');
 }, klass = SizeMatchExpression, base =  ArrayMatchingMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// File: expression_array.h lines: 131-131
 proto._size = undefined;
 
 /**
@@ -19,8 +15,13 @@ proto._size = undefined;
  *
  */
 proto.debugString = function debugString(level) {
-	// File: expression_array.cpp lines: 259-261
-	return this._debugAddSpace( level ) + this.path() + " $size : " + this._size.toString() + "\n";
+	var debug = this._debugAddSpace( level ) + this.path() + " $size : " + this._size.toString() + "\n";
+	
+	var td = this.tagData();
+	if (td !== null){
+		debug += " " + td.debugString();
+	}
+	return debug;
 };
 
 /**
@@ -31,11 +32,10 @@ proto.debugString = function debugString(level) {
  *
  */
 proto.equivalent = function equivalent(other) {
-	// File: expression_array.cpp lines: 264-269
-	if(other._matchType != 'SIZE') {
+	if(other.matchType() !== this.matchType()) {
 		return false;
 	}
-	return this._size == other._size && this._path == other._path;
+	return this._size === other._size && this.path() === other.path();
 };
 
 /**
@@ -45,7 +45,6 @@ proto.equivalent = function equivalent(other) {
  *
  */
 proto.getData = function getData(){
-	// File: expression_array.h lines: 128-127
 	return this._size;
 };
 
@@ -58,9 +57,6 @@ proto.getData = function getData(){
  *
  */
 proto.init = function init(path,size) {
-	// File: expression_array.cpp lines: 248-250
-	if(size === null)
-		return {code:'BAD_VALUE', 'description':'Cannot assign null to size'};
 	this._size = size;
 	return this.initPath(path);
 };
@@ -74,11 +70,10 @@ proto.init = function init(path,size) {
  *
  */
 proto.matchesArray = function matchesArray(anArray, details) {
-	// File: expression_array.cpp lines: 253-256
 	if(this._size < 0) {
 		return false;
 	}
-	return anArray.length == this._size;
+	return anArray.length === this._size;
 };
 
 /**
@@ -91,6 +86,8 @@ proto.shallowClone = function shallowClone(){
 	// File: expression_array.h lines: 116-119
 	var e = new SizeMatchExpression();
 	e.init(this.path(),this._size);
+	if ( this.getTag() ) {
+		e.setTag(this.getTag().clone());
+	}
 	return e;
 };
-

+ 0 - 55
lib/pipeline/matcher/TagData.js

@@ -1,55 +0,0 @@
-"use strict";
-
-
-
-// Autogenerated by cport.py on 2013-09-17 14:37
-var TagData = module.exports = function TagData (){
-
-}, klass = TagData, base =  Object  , proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-
-
-
-
-/**
- * debugString pure virtual.
- *
- * @method debugString
- *
- */
-proto.debugString = function debugString() {
-	throw new Error('Virtual function called.');
-};
-
-// Below this line is manually generated based on inferred info in the source.
-
-/**
- * resets the tag data.
- *
- *@method reset
- */
-proto.reset = function reset( data ) {
-	this._data = data;
-};
-
-/**
- * Sets the data tag
- * @method set
- * @param data
- */
-proto.set = function set( data ) {
-	this._data = data;
-};
-
-
-
-/**
- * Gets the data tag
- *
- * @method get
- *
- */
-proto.get = function get(){
-	return this._data;
-};
-

+ 112 - 0
lib/pipeline/matcher/TextMatchExpression.js

@@ -0,0 +1,112 @@
+"use strict";
+
+var LeafMatchExpression = require('./LeafMatchExpression.js');
+
+var TextMatchExpression = module.exports = function TextMatchExpression() {
+	base.call(this, 'TEXT');
+}, klass = TextMatchExpression, base = LeafMatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
+
+/**
+ *
+ * Initializes the class object.
+ *
+ * @param query
+ * @param language
+ * @returns {*}
+ */
+proto.init = function init(query, language) {
+	this._query = query;
+	this._language = language;
+
+	return this.initPath('_fts');
+};
+
+/**
+ * Gets the query.
+ *
+ * @returns {*}
+ */
+proto.getQuery = function getQuery() {
+	return this._query;
+};
+
+/**
+ * Gets the language.
+ *
+ * @returns {*}
+ */
+proto.getLanguage = function getLanguage() {
+	return this._language;
+};
+
+/**
+ * Check if the input element matches.
+ *
+ * @param e
+ * @returns {boolean}
+ */
+proto.matchesSingleElement = function matchesSingleElement(e) {
+	return true;
+};
+
+/**
+ * Debug a string.
+ *
+ * @param level
+ * @returns {string}
+ */
+proto.debugString = function debugString(level) {
+	var rtn = this._debugAddSpace(level);
+
+	rtn += 'TEXT : query=' + ', language=' + this._language + ', tag=';
+
+	var tagData = this.getTag();
+
+	if (tagData !== null) {
+		tagData.debugString(level);
+	} else {
+		rtn += 'NULL';
+	}
+
+	return rtn + '\n';
+};
+
+/**
+ * Verifies the equivalency of two operands.
+ *
+ * @param other
+ * @returns {boolean}
+ */
+proto.equivalent = function equivalent(other) {
+	if (this.matchType() !== other.matchType()) {
+		return false;
+	}
+
+	if (other.getQuery() !== this._query) {
+		return false;
+	}
+
+	if (other.getLanguage() !== this._language) {
+		return false;
+	}
+
+	return true;
+};
+
+/**
+ * Clone this instance into a new one.
+ *
+ * @returns {TextMatchExpression}
+ */
+proto.shallowClone = function shallowClone() {
+	var next = new TextMatchExpression();
+
+	next.init(this._query, this._language);
+
+	if (this.getTag()) {
+		next.getTag(this.getTag().clone());
+	}
+
+	return next;
+};
+

+ 25 - 0
lib/pipeline/matcher/TextMatchExpressionParser.js

@@ -0,0 +1,25 @@
+/**
+ * Expression parser's text callback function.
+ *
+ * @param queryObj
+ * @returns {*}
+ * @private
+ */
+var _expressionParserTextCallbackReal = function _expressionParserTextCallbackReal(queryObj) {
+	if (queryObj.$search._type !== 'string') {
+		return {code: ErrorCodes.BadValue, description: '$search needs a String'};
+	}
+
+	var e = new TextMatchExpression(),
+		s = e.init(query, language);
+
+	if (s.code !== 'OK') {
+		return s;
+	}
+
+	return e.release();
+};
+
+module.exports = {
+	expressionParserTextCallbackReal: _expressionParserTextCallbackReal
+};

+ 97 - 98
lib/pipeline/matcher/TypeMatchExpression.js

@@ -1,163 +1,162 @@
 "use strict";
+
 var MatchExpression = require('./MatchExpression');
 var ElementPath = require('./ElementPath');
 
-// Autogenerated by cport.py on 2013-09-17 14:37
-var TypeMatchExpression = module.exports = function TypeMatchExpression(){
-	base.call(this);
-	this._elementPath = new ElementPath();
-	this._matchType = 'TYPE_OPERATOR';
-}, klass = TypeMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+var TypeMatchExpression = module.exports = function TypeMatchExpression() {
+	base.call(this, 'TYPE_OPERATOR');
 
+	this._elementPath = new ElementPath();
+}, klass = TypeMatchExpression, base =  MatchExpression, proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
 
-// File: expression_leaf.h lines: 338-338
 proto._elementPath = undefined;
-
-// File: expression_leaf.h lines: 337-337
 proto._path = undefined;
-
-// File: expression_leaf.h lines: 339-339
 proto._type = undefined;
 
 /**
+ * Initialize the current object.
  *
- * Writes a debug string for this object
- * @method debugString
- * @param level
- *
+ * @param path
+ * @param type
+ * @returns {*}
  */
-proto.debugString = function debugString(level) {
-	// File: expression_leaf.cpp lines: 335-343
-	return this._debugAddSapce( level ) + this.path() + " type: " + this._type + (this.getTag() ? this.getTag().debugString() : '' ) + "\n";
+proto.init = function init(path, type) {
+	this._path = path;
+	this._type = type;
+
+	return this._elementPath.init(path);
 };
 
 /**
  *
- * checks if this expression is == to the other
- * @method equivalent
- * @param other
+ * Returns a shallow copy of the instance.
  *
+ * @returns {TypeMatchExpression}
  */
-proto.equivalent = function equivalent(other) {
-	// File: expression_leaf.cpp lines: 347-352
-	if( other._matchType != 'TYPE_OPERATOR' ) {
-		return false;
+proto.shallowClone = function shallowClone(){
+	var clone = new TypeMatchExpression();
+
+	clone.init(this._path, this._type);
+
+	if (this.getTag()) {
+		clone.setTag(this.getTag().clone());
 	}
-	return this._path == other._path && this._type == other._type;
+
+	return clone;
 };
 
 /**
+ * Used number reference to types like the C++ enum (?).
  *
- * Return the _type property
- * @method getData
- *
+ * @param e
+ * @returns {number}
  */
-proto.getData = function getData(){
-	// File: expression_leaf.h lines: 328-327
-	return this._type;
+klass.type = function type(e) {
+	if(e === undefined) {
+		return 6;
+	} else if (e === null) {
+		return 10;
+	}
+
+	switch (typeof e) {
+		case 'number':
+			return 1;
+		case 'string':
+			return 2;
+		case 'boolean':
+			return 8;
+		case 'object':
+			if (e instanceof Array) {
+				return 4;
+			} else if (e instanceof RegExp) {
+				return 11;
+			} else if (e instanceof Date) {
+				return 9;
+			} else if (e.constructor.name === 'MinKey') {
+				return -1;
+			} else if (e.constructor.name === 'MaxKey') {
+				return 127;
+			}
+	}
+
+	return 42;
 };
 
 /**
+ * Matches single element.
  *
- * Initialize the necessary items
- * @method init
- * @param path
- * @param type
- *
+ * @param e
+ * @returns {boolean}
  */
-proto.init = function init(path,type) {
-	// File: expression_leaf.cpp lines: 308-311
-	this._path = path;
-	this._type = type;
-	return this._elementPath.init( path );
+proto.matchesSingleElement = function matchesSingleElement(e) {
+	return klass.type(e) === this._type;
 };
 
 /**
+ * Matches against the document.
  *
- * matches checks the input doc against the internal element path to see if it is a match
- * @method matches
  * @param doc
  * @param details
- *
+ * @returns {*}
  */
-proto.matches = function matches(doc,details) {
-	// File: expression_leaf.cpp lines: 318-332
+proto.matches = function matches(doc, details) {
 	var self = this,
-		checker = function(element) {
-			if (!self.matchesSingleElement(element, details))
+		matcher = function matcher(element) {
+			if (!self.matchesSingleElement(element, details)) {
 				return false;
+			}
 
-			//var amIRoot = (element.length === 0);		
 			return true;
 		};
-	return this._elementPath._matches(doc, details, checker);
+
+	return this._elementPath._matches(doc, details, matcher);
 };
 
 /**
- * Return a number indicating the type of the input element
- * @method typeNumber
- * @param e
+ * Writes a debug string for this object.
  *
+ * @param level
+ * @returns {string}
  */
-klass.typeNumber = function typeNumber(e){
-	if(e === undefined)
-		return 6;
-	if(e === null)
-		return 10;
-	if(typeof(e) == 'number')
-		return 1;
-	if(typeof(e) == 'string')
-		return 2;
-	if(typeof(e) == 'boolean')
-		return 8;
-	if(typeof(e) == 'object') {
-		if(e instanceof Array)
-			return 4;
-		if(e instanceof RegExp)
-			return 11;
-		if(e instanceof Date)
-			return 9;
-		if(e.constructor.name == 'MinKey')
-			return -1;
-		if(e.constructor.name == 'MAxKey')
-			return 127;
+proto.debugString = function debugString(level) {
+	var rtn  = this._debugAddSpace(level) + this.path() + ' type: ' + this._type;
+
+	if (this.getTag()) {
+		rtn += ' ' + this.getTag().debugString();
 	}
-	return 42; // Can't tell
+
+	return rtn + '\n';
 };
 
 /**
+ * Checks to see if the other instance is equivalent to the current instance.
  *
- * Check if the input element matches
- * @method matchesSingleElement
- * @param e
- *
+ * @param other
+ * @returns {boolean}
  */
-proto.matchesSingleElement = function matchesSingleElement(e) {
-	// File: expression_leaf.cpp lines: 314-315
-	return klass.typeNumber( e ) == this._type;
+proto.equivalent = function equivalent(other) {
+	if (this.matchType() !== other.matchType()) {
+		return false;
+	}
+
+	return (this._path === other._path && this._type === other._type);
 };
 
 /**
+ * Return the type we're matching against.
  *
- * return the internal path
- * @method path
- *
+ * @returns {undefined|*}
  */
-proto.path = function path(){
-	// File: expression_leaf.h lines: 330-329
-	return this._path;
+proto.getData = function getData() {
+	return this._type;
 };
 
 /**
+ * Return the path associated with the current object.
  *
- * clone this instance to a new one
- * @method shallowClone
- *
+ * @returns {undefined|*|klass._path}
  */
-proto.shallowClone = function shallowClone(){
-	// File: expression_leaf.h lines: 311-314
-	var e = new TypeMatchExpression();
-	e.init( this._path,this._type );
-	return e;
+proto.path = function path() {
+	return this._path;
 };
 

+ 4 - 2
package.json

@@ -27,13 +27,15 @@
     "alteration"
   ],
   "dependencies": {
-    "async": "*"
+    "async": "*",
+    "xregexp": "*"
   },
   "devDependencies": {
     "mocha": "*",
     "jshint": "*",
     "jscoverage": "*",
-    "jscheckstyle": "*"
+    "jscheckstyle": "*",
+    "bson": "0.2.15"
   },
   "license": "AGPL",
   "private": true,

+ 68 - 71
test/lib/pipeline/accumulators/AddToSetAccumulator.js

@@ -2,95 +2,92 @@
 var assert = require("assert"),
 	AddToSetAccumulator = require("../../../../lib/pipeline/accumulators/AddToSetAccumulator");
 
-
-var createAccumulator = function createAccumulator() {
-	return new AddToSetAccumulator();
+// 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));
+
+var testData = {
+	nil: null,
+	bF: false, bT: true,
+	numI: 123, numF: 123.456,
+	str: "TesT! mmm π",
+	obj: {foo:{bar:"baz"}},
+	arr: [1, 2, 3, [4, 5, 6]],
+	date: new Date(),
+	re: /foo/gi,
 };
 
 //TODO: refactor these test cases using Expression.parseOperand() or something because these could be a whole lot cleaner...
-module.exports = {
+exports.AddToSetAccumulator = {
 
-	"AddToSetAccumulator": {
+	".constructor()": {
 
-		"constructor()": {
+		"should create instance of Accumulator": function() {
+			assert(new AddToSetAccumulator() instanceof AddToSetAccumulator);
+		},
 
-			"should error if called with args": function testArgsGivenToCtor() {
-				assert.throws(function() {
-					new AddToSetAccumulator('arg');
-				});
-			},
+		"should error if called with args": function() {
+			assert.throws(function() {
+				new AddToSetAccumulator(123);
+			});
+		}
 
-			"should construct object with set property": function testCtorAssignsSet() {
-				var acc = new AddToSetAccumulator();
-				assert.notEqual(acc.set, null);
-				assert.notEqual(acc.set, undefined);
-			}
+	},
 
-		},
+	".create()": {
 
-		"#getFactory()": {
+		"should return an instance of the accumulator": function() {
+			assert(AddToSetAccumulator.create() instanceof AddToSetAccumulator);
+		}
+
+	},
 
-			"should return the constructor for this class": function factoryIsConstructor(){
-				assert.strictEqual(new AddToSetAccumulator().getFactory(), AddToSetAccumulator);
-			}
+	"#process()": {
 
+		"should add input to set": function() {
+			var acc = AddToSetAccumulator.create();
+			acc.process(testData);
+			assert.deepEqual(acc.getValue(), [testData]);
 		},
 
-		"#processInternal()" : {
-			"should add input to set": function testAddsToSet() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				var value = acc.getValue();
-				assert.deepEqual(JSON.stringify(value), JSON.stringify([5]));
-			}
+		"should add input iff not already in set": function() {
+			var acc = AddToSetAccumulator.create();
+			acc.process(testData);
+			acc.process(testData);
+			assert.deepEqual(acc.getValue(), [testData]);
+		},
 
+		"should merge input into set": function() {
+			var acc = AddToSetAccumulator.create();
+			acc.process(testData);
+			acc.process([testData, 42], true);
+			assert.deepEqual(acc.getValue(), [42, testData]);
 		},
 
-		"#getValue()": {
-
-			"should return empty array": function testEmptySet() {
-				var acc = new createAccumulator();
-				var value = acc.getValue();
-				assert.equal((value instanceof Array), true);
-				assert.equal(value.length, 0);
-			},
-
-			"should return array with one element that equals 5": function test5InSet() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				acc.processInternal(5);
-				var value = acc.getValue();
-				assert.deepEqual(JSON.stringify(value), JSON.stringify([5]));
-			},
-
-			"should produce value that is an array of multiple elements": function testMultipleItems() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				acc.processInternal({key: "value"});
-				var value = acc.getValue();
-				assert.deepEqual(JSON.stringify(value), JSON.stringify([5, {key: "value"}]));
-			},
-
-			"should return array with one element that is an object containing a key/value pair": function testKeyValue() {
-				var acc = createAccumulator();
-				acc.processInternal({key: "value"});
-				var value = acc.getValue();
-				assert.deepEqual(JSON.stringify(value), JSON.stringify([{key: "value"}]));
-			},
-
-			"should coalesce different instances of equivalent objects": function testGetValue_() {
-				var acc = createAccumulator();
-				acc.processInternal({key: "value"});
-				acc.processInternal({key: "value"});
-				var value = acc.getValue();
-				assert.deepEqual(JSON.stringify(value), JSON.stringify([{key: "value"}]));
-			}
+	},
 
-		}
+	"#getValue()": {
 
-	}
+		"should return empty set initially": function() {
+			var acc = new AddToSetAccumulator.create();
+			var value = acc.getValue();
+			assert.equal((value instanceof Array), true);
+			assert.equal(value.length, 0);
+		},
 
-};
+		"should return set of added items": function() {
+			var acc = AddToSetAccumulator.create(),
+				expected = [
+					42,
+					{foo:1, bar:2},
+					{bar:2, foo:1},
+					testData
+				];
+			expected.forEach(function(input){
+				acc.process(input);
+			});
+			assert.deepEqual(acc.getValue(), expected);
+		},
 
+	}
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+};

+ 192 - 73
test/lib/pipeline/accumulators/AvgAccumulator.js

@@ -2,111 +2,230 @@
 var assert = require("assert"),
 	AvgAccumulator = require("../../../../lib/pipeline/accumulators/AvgAccumulator");
 
-function createAccumulator(){
-	return new AvgAccumulator();
-}
+// 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));
 
-module.exports = {
+exports.AvgAccumulator = {
 
-	"AvgAccumulator": {
-
-		"constructor()": {
-
-			"should not throw Error when constructing without args": function testConstructor(){
-				assert.doesNotThrow(function(){
-					new AvgAccumulator();
-				});
-			}
+	".constructor()": {
 
+		"should not throw Error when constructing without args": function() {
+			new AvgAccumulator();
 		},
 
-		"#getOpName()": {
+	},
+
+	"#process()": {
 
-			"should return the correct op name; $avg": function testOpName(){
-				assert.strictEqual(new AvgAccumulator().getOpName(), "$avg");
-			}
+		"should allow numbers": function() {
+			assert.doesNotThrow(function() {
+				var acc = AvgAccumulator.create();
+				acc.process(1);
+			});
+		},
 
+		"should ignore non-numbers": function() {
+			assert.doesNotThrow(function() {
+				var acc = AvgAccumulator.create();
+				acc.process(true);
+				acc.process("Foo");
+				acc.process(new Date());
+				acc.process({});
+				acc.process([]);
+			});
 		},
 
-		"#processInternal()": {
+		"router": {
 
-			"should evaluate no documents": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				assert.strictEqual(avgAccumulator.getValue(), 0);
+			"should handle result from one shard": function testOneShard() {
+				var acc = AvgAccumulator.create();
+				acc.process({subTotal:3.0, count:2}, true);
+				assert.deepEqual(acc.getValue(), 3.0 / 2);
 			},
 
-			"should evaluate one document with a field that is NaN": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(Number("foo"));
-				// NaN is unequal to itself
-				assert.notStrictEqual(avgAccumulator.getValue(), avgAccumulator.getValue());
+			"should handle result from two shards": function testTwoShards() {
+				var acc = AvgAccumulator.create();
+				acc.process({subTotal:6.0, count:1}, true);
+				acc.process({subTotal:5.0, count:2}, true);
+				assert.deepEqual(acc.getValue(), 11.0 / 3);
 			},
 
+		},
+
+	},
+
+	".create()": {
+
+		"should create an instance": function() {
+			assert(AvgAccumulator.create() instanceof AvgAccumulator);
+		},
+
+	},
+
+	"#getValue()": {
 
-			"should evaluate one document and avg it's value": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(5);
-				assert.strictEqual(avgAccumulator.getValue(), 5);
+		"should return 0 if no inputs evaluated": function testNoDocsEvaluated() {
+			var acc = AvgAccumulator.create();
+			assert.equal(acc.getValue(), 0);
+		},
+
+		"should return one int": function testOneInt() {
+			var acc = AvgAccumulator.create();
+			acc.process(3);
+			assert.equal(acc.getValue(), 3);
+		},
+
+		"should return one long": function testOneLong() {
+			var acc = AvgAccumulator.create();
+			acc.process(-4e24);
+			assert.equal(acc.getValue(), -4e24);
+		},
+
+		"should return one double": function testOneDouble() {
+			var acc = AvgAccumulator.create();
+			acc.process(22.6);
+			assert.equal(acc.getValue(), 22.6);
+		},
+
+		"should return avg for two ints": function testIntInt() {
+			var acc = AvgAccumulator.create();
+			acc.process(10);
+			acc.process(11);
+			assert.equal(acc.getValue(), 10.5);
+		},
+
+		"should return avg for int and double": function testIntDouble() {
+			var acc = AvgAccumulator.create();
+			acc.process(10);
+			acc.process(11.0);
+			assert.equal(acc.getValue(), 10.5);
+		},
 
+		"should return avg for two ints w/o overflow": function testIntIntNoOverflow() {
+			var acc = AvgAccumulator.create();
+			acc.process(32767);
+			acc.process(32767);
+			assert.equal(acc.getValue(), 32767);
+		},
+
+		"should return avg for two longs w/o overflow": function testLongLongOverflow() {
+			var acc = AvgAccumulator.create();
+			acc.process(2147483647);
+			acc.process(2147483647);
+			assert.equal(acc.getValue(), (2147483647 + 2147483647) / 2);
+		},
+
+		"shard": {
+
+			"should return avg info for int": function testShardInt() {
+				var acc = AvgAccumulator.create();
+				acc.process(3);
+				assert.deepEqual(acc.getValue(true), {subTotal:3.0, count:1});
 			},
 
+			"should return avg info for long": function testShardLong() {
+				var acc = AvgAccumulator.create();
+				acc.process(5);
+				assert.deepEqual(acc.getValue(true), {subTotal:5.0, count:1});
+			},
 
-			"should evaluate and avg two ints": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(5);
-				avgAccumulator.processInternal(7);
-				assert.strictEqual(avgAccumulator.getValue(), 6);
+			"should return avg info for double": function testShardDouble() {
+				var acc = AvgAccumulator.create();
+				acc.process(116.0);
+				assert.deepEqual(acc.getValue(true), {subTotal:116.0, count:1});
 			},
 
-			"should evaluate and avg two ints overflow": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(Number.MAX_VALUE);
-				avgAccumulator.processInternal(Number.MAX_VALUE);
-				assert.strictEqual(Number.isFinite(avgAccumulator.getValue()), false);
+			beforeEach: function() { // used in the tests below
+				this.getAvgValueFor = function(a, b) { // kind of like TwoOperandBase
+					var acc = AvgAccumulator.create();
+					for (var i = 0, l = arguments.length; i < l; i++) {
+						acc.process(arguments[i]);
+					}
+					return acc.getValue(true);
+				};
 			},
 
+			"should return avg info for two ints w/ overflow": function testShardIntIntOverflow() {
+				var operand1 = 32767,
+					operand2 = 3,
+					expected = {subTotal: 32767 + 3.0, count: 2};
+				assert.deepEqual(this.getAvgValueFor(operand1, operand2), expected);
+				assert.deepEqual(this.getAvgValueFor(operand2, operand1), expected);
+			},
 
-			"should evaluate and avg two negative ints": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(-5);
-				avgAccumulator.processInternal(-7);
-				assert.strictEqual(avgAccumulator.getValue(), -6);
+			"should return avg info for int and long": function testShardIntLong() {
+				var operand1 = 5,
+					operand2 = 3e24,
+					expected = {subTotal: 5 + 3e24, count: 2};
+				assert.deepEqual(this.getAvgValueFor(operand1, operand2), expected);
+				assert.deepEqual(this.getAvgValueFor(operand2, operand1), expected);
 			},
 
-//TODO Not sure how to do this in Javascript
-//			"should evaluate and avg two negative ints overflow": function testStuff(){
-//				var avgAccumulator = createAccumulator();
-//				avgAccumulator.processInternal(Number.MIN_VALUE);
-//				avgAccumulator.processInternal(7);
-//				assert.strictEqual(avgAccumulator.getValue(), Number.MAX_VALUE);
-//			},
-//
-
-			"should evaluate and avg int and float": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(8.5);
-				avgAccumulator.processInternal(7);
-				assert.strictEqual(avgAccumulator.getValue(), 7.75);
+			"should return avg info for int and double": function testShardIntDouble() {
+				var operand1 = 5,
+					operand2 = 6.2,
+					expected = {subTotal: 5 + 6.2, count: 2};
+				assert.deepEqual(this.getAvgValueFor(operand1, operand2), expected);
+				assert.deepEqual(this.getAvgValueFor(operand2, operand1), expected);
 			},
 
-			"should evaluate and avg one Number and a NaN sum to NaN": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(8);
-				avgAccumulator.processInternal(Number("bar"));
-				// NaN is unequal to itself
-				assert.notStrictEqual(avgAccumulator.getValue(), avgAccumulator.getValue());
+			"should return avg info for long and double": function testShardLongDouble() {
+				var operand1 = 5e24,
+					operand2 = 1.0,
+					expected = {subTotal: 5e24 + 1.0, count: 2};
+				assert.deepEqual(this.getAvgValueFor(operand1, operand2), expected);
+				assert.deepEqual(this.getAvgValueFor(operand2, operand1), expected);
 			},
 
-			"should evaluate and avg a null value to 0": function testStuff(){
-				var avgAccumulator = createAccumulator();
-				avgAccumulator.processInternal(null);
-				assert.strictEqual(avgAccumulator.getValue(), 0);
-			}
+			"should return avg info for int and long and double": function testShardIntLongDouble() {
+				var operand1 = 1,
+					operand2 = 2e24,
+					operand3 = 4.0,
+					expected = {subTotal: 1 + 2e24 + 4.0, count: 3};
+				assert.deepEqual(this.getAvgValueFor(operand1, operand2, operand3), expected);
+			},
 
+		},
+
+		"should handle NaN": function() {
+			var acc = AvgAccumulator.create();
+			acc.process(NaN);
+			acc.process(1);
+			assert(isNaN(acc.getValue()));
+			acc = AvgAccumulator.create();
+			acc.process(1);
+			acc.process(NaN);
+			assert(isNaN(acc.getValue()));
+		},
+
+		"should handle null as 0": function() {
+			var acc = AvgAccumulator.create();
+			acc.process(null);
+			assert.equal(acc.getValue(), 0);
 		}
 
-	}
+	},
 
-};
+	"#reset()": {
+
+		"should reset to zero": function() {
+			var acc = AvgAccumulator.create();
+			assert.equal(acc.getValue(), 0);
+			acc.process(123);
+			assert.notEqual(acc.getValue(), 0);
+			acc.reset();
+			assert.equal(acc.getValue(), 0);
+			assert.deepEqual(acc.getValue(true), {subTotal:0, count:0});
+		}
+
+	},
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+	"#getOpName()": {
+
+		"should return the correct op name; $avg": function() {
+			assert.equal(new AvgAccumulator().getOpName(), "$avg");
+		}
+
+	},
+
+};

+ 77 - 55
test/lib/pipeline/accumulators/FirstAccumulator.js

@@ -2,77 +2,99 @@
 var assert = require("assert"),
 	FirstAccumulator = require("../../../../lib/pipeline/accumulators/FirstAccumulator");
 
-function createAccumulator(){
-	return new FirstAccumulator();
-}
+// 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));
 
-module.exports = {
+exports.FirstAccumulator = {
 
-	"FirstAccumulator": {
+	".constructor()": {
 
-		"constructor()": {
+		"should create instance of Accumulator": function() {
+			assert(new FirstAccumulator() instanceof FirstAccumulator);
+		},
+
+		"should throw error if called with args": function() {
+			assert.throws(function() {
+				new FirstAccumulator(123);
+			});
+		},
 
-			"should not throw Error when constructing without args": function testConstructor(){
-				assert.doesNotThrow(function(){
-					new FirstAccumulator();
-				});
-			}
+	},
 
+	".create()": {
+
+		"should return an instance of the accumulator": function() {
+			assert(FirstAccumulator.create() instanceof FirstAccumulator);
 		},
 
-		"#getOpName()": {
+	},
+
+	"#process()": {
 
-			"should return the correct op name; $first": function testOpName(){
-				assert.equal(new FirstAccumulator().getOpName(), "$first");
-			}
+		"should return undefined if no inputs evaluated": function testNone() {
+			var acc = FirstAccumulator.create();
+			assert.strictEqual(acc.getValue(), undefined);
+		},
 
+		"should return value for one input": function testOne() {
+			var acc = FirstAccumulator.create();
+			acc.process(5);
+			assert.strictEqual(acc.getValue(), 5);
 		},
 
-		"#getFactory()": {
+		"should return missing for one missing input": function testMissing() {
+			var acc = FirstAccumulator.create();
+			acc.process(undefined);
+			assert.strictEqual(acc.getValue(), undefined);
+		},
 
-			"should return the constructor for this class": function factoryIsConstructor(){
-				assert.strictEqual(new FirstAccumulator().getFactory(), FirstAccumulator);
-			}
+		"should return first of two inputs": function testTwo() {
+			var acc = FirstAccumulator.create();
+			acc.process(5);
+			acc.process(7);
+			assert.strictEqual(acc.getValue(), 5);
+		},
 
+		"should return first of two inputs (even if first is missing)": function testFirstMissing() {
+			var acc = FirstAccumulator.create();
+			acc.process(undefined);
+			acc.process(7);
+			assert.strictEqual(acc.getValue(), undefined);
 		},
 
-		"#processInternal()": {
-
-			"The accumulator has no value": function none() {
-				// The accumulator returns no value in this case.
-				var acc = createAccumulator();
-				assert.ok(!acc.getValue());
-			},
-
-			"The accumulator uses processInternal on one input and retains its value": function one() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				assert.strictEqual(acc.getValue(), 5);
-			},
-
-			"The accumulator uses processInternal on one input with the field missing and retains undefined": function missing() {
-				var acc = createAccumulator();
-				acc.processInternal();
-				assert.strictEqual(acc.getValue(), undefined);
-			},
-
-			"The accumulator uses processInternal on two inputs and retains the value in the first": function two() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				acc.processInternal(7);
-				assert.strictEqual(acc.getValue(), 5);
-			},
-
-			"The accumulator uses processInternal on two inputs and retains the undefined value in the first": function firstMissing() {
-				var acc = createAccumulator();
-				acc.processInternal();
-				acc.processInternal(7);
-				assert.strictEqual(acc.getValue(), undefined);
-			}
+	},
+
+	"#getValue()": {
+
+		"should get value the same for shard and router": function() {
+			var acc = FirstAccumulator.create();
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			acc.process(123);
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+		},
+
+	},
+
+	"#reset()": {
+
+		"should reset to missing": function() {
+			var acc = FirstAccumulator.create();
+			assert.strictEqual(acc.getValue(), undefined);
+			acc.process(123);
+			assert.notEqual(acc.getValue(), undefined);
+			acc.reset();
+			assert.strictEqual(acc.getValue(), undefined);
+			assert.strictEqual(acc.getValue(true), undefined);
 		}
 
-	}
+	},
 
-};
+	"#getOpName()": {
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+		"should return the correct op name; $first": function() {
+			assert.equal(new FirstAccumulator().getOpName(), "$first");
+		}
+
+	},
+
+};

+ 71 - 44
test/lib/pipeline/accumulators/LastAccumulator.js

@@ -2,73 +2,100 @@
 var assert = require("assert"),
 	LastAccumulator = require("../../../../lib/pipeline/accumulators/LastAccumulator");
 
-function createAccumulator(){
-	return new LastAccumulator();
-}
+// 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.LastAccumulator = {
 
-module.exports = {
+	".constructor()": {
 
-	"LastAccumulator": {
+		"should create instance of Accumulator": function() {
+			assert(new LastAccumulator() instanceof LastAccumulator);
+		},
+
+		"should throw error if called with args": function() {
+			assert.throws(function() {
+				new LastAccumulator(123);
+			});
+		},
 
-		"constructor()": {
+	},
 
-			"should not throw Error when constructing without args": function testConstructor(){
-				assert.doesNotThrow(function(){
-					new LastAccumulator();
-				});
-			}
+	".create()": {
 
+		"should return an instance of the accumulator": function() {
+			assert(LastAccumulator.create() instanceof LastAccumulator);
 		},
 
-		"#getOpName()": {
+	},
+
+	"#process()": {
+
+		"should return undefined if no inputs evaluated": function testNone() {
+			var acc = LastAccumulator.create();
+			assert.strictEqual(acc.getValue(), undefined);
+		},
 
-			"should return the correct op name; $last": function testOpName(){
-				assert.strictEqual(new LastAccumulator().getOpName(), "$last");
-			}
+		"should return value for one input": function testOne() {
+			var acc = LastAccumulator.create();
+			acc.process(5);
+			assert.strictEqual(acc.getValue(), 5);
+		},
 
+		"should return missing for one missing input": function testMissing() {
+			var acc = LastAccumulator.create();
+			acc.process(undefined);
+			assert.strictEqual(acc.getValue(), undefined);
 		},
 
-		"#processInternal()": {
+		"should return last of two inputs": function testTwo() {
+			var acc = LastAccumulator.create();
+			acc.process(5);
+			acc.process(7);
+			assert.strictEqual(acc.getValue(), 7);
+		},
 
-			"should evaluate no documents": function testStuff(){
-				var lastAccumulator = createAccumulator();
-				assert.strictEqual(lastAccumulator.getValue(), undefined);
-			},
+		"should return last of two inputs (even if last is missing)": function testFirstMissing() {
+			var acc = LastAccumulator.create();
+			acc.process(7);
+			acc.process(undefined);
+			assert.strictEqual(acc.getValue(), undefined);
+		},
 
+	},
 
-			"should evaluate one document and retains its value": function testStuff(){
-				var lastAccumulator = createAccumulator();
-				lastAccumulator.processInternal(5);
-				assert.strictEqual(lastAccumulator.getValue(), 5);
+	"#getValue()": {
 
-			},
+		"should get value the same for shard and router": function() {
+			var acc = LastAccumulator.create();
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			acc.process(123);
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+		},
 
+	},
 
-			"should evaluate one document with the field missing retains undefined": function testStuff(){
-				var lastAccumulator = createAccumulator();
-				lastAccumulator.processInternal();
-				assert.strictEqual(lastAccumulator.getValue(), undefined);
-			},
+	"#reset()": {
 
+		"should reset to missing": function() {
+			var acc = LastAccumulator.create();
+			assert.strictEqual(acc.getValue(), undefined);
+			acc.process(123);
+			assert.notEqual(acc.getValue(), undefined);
+			acc.reset();
+			assert.strictEqual(acc.getValue(), undefined);
+			assert.strictEqual(acc.getValue(true), undefined);
+		},
 
-			"should evaluate two documents and retains the value in the last": function testStuff(){
-				var lastAccumulator = createAccumulator();
-				lastAccumulator.processInternal(5);
-				lastAccumulator.processInternal(7);
-				assert.strictEqual(lastAccumulator.getValue(), 7);
-			},
+	},
 
+	"#getOpName()": {
 
-			"should evaluate two documents and retains the undefined value in the last": function testStuff(){
-				var lastAccumulator = createAccumulator();
-				lastAccumulator.processInternal(5);
-				lastAccumulator.processInternal();
-				assert.strictEqual(lastAccumulator.getValue(), undefined);
-			}
-		}
+		"should return the correct op name; $last": function() {
+			assert.equal(new LastAccumulator().getOpName(), "$last");
+		},
 
-	}
+	},
 
 };
 

+ 0 - 78
test/lib/pipeline/accumulators/MaxAccumulator.js

@@ -1,78 +0,0 @@
-"use strict";
-var assert = require("assert"),
-	MaxAccumulator = require("../../../../lib/pipeline/accumulators/MinMaxAccumulator");
-
-function createAccumulator(){
-	return MaxAccumulator.createMax();
-}
-
-
-module.exports = {
-
-	"MaxAccumulator": {
-
-		"constructor()": {
-
-			"should not throw Error when constructing without args using createMax": function testConstructor(){
-				assert.doesNotThrow(function(){
-					MaxAccumulator.createMax();
-				});
-			},
-
-			"should throw Error when constructing without args using default constructor": function testConstructor(){
-				assert.throws(function(){
-					new MaxAccumulator();
-				});
-			}
-
-		},
-
-		"#getOpName()": {
-
-			"should return the correct op name; $max": function testOpName(){
-				var acc = createAccumulator();
-				assert.equal(acc.getOpName(), "$max");
-			}
-
-		},
-
-		"#processInternal()": {
-
-			"The accumulator evaluates no documents": function none() {
-				// The accumulator returns no value in this case.
-				var acc = createAccumulator();
-				assert.ok(!acc.getValue());
-			},
-
-			"The accumulator evaluates one document and retains its value": function one() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				assert.strictEqual(acc.getValue(), 5);
-			},
-
-			"The accumulator evaluates one document with the field missing retains undefined": function missing() {
-				var acc = createAccumulator();
-				acc.processInternal();
-				assert.strictEqual(acc.getValue(), undefined);
-			},
-
-			"The accumulator evaluates two documents and retains the maximum": function two() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				acc.processInternal(7);
-				assert.strictEqual(acc.getValue(), 7);
-			},
-
-			"The accumulator evaluates two documents and retains the defined value in the first": function lastMissing() {
-				var acc = createAccumulator();
-				acc.processInternal(7);
-				acc.processInternal();
-				assert.strictEqual(acc.getValue(), 7);
-			}
-		}
-
-	}
-
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 0 - 78
test/lib/pipeline/accumulators/MinAccumulator.js

@@ -1,78 +0,0 @@
-"use strict";
-var assert = require("assert"),
-	MinAccumulator = require("../../../../lib/pipeline/accumulators/MinMaxAccumulator");
-
-function createAccumulator(){
-	return MinAccumulator.createMin();
-}
-
-
-module.exports = {
-
-	"MinAccumulator": {
-
-		"constructor()": {
-
-			"should not throw Error when constructing without args using createMin": function testConstructor(){
-				assert.doesNotThrow(function(){
-					MinAccumulator.createMin();
-				});
-			},
-
-			"should throw Error when constructing without args using default constructor": function testConstructor(){
-				assert.throws(function(){
-					new MinAccumulator();
-				});
-			}
-
-		},
-
-		"#getOpName()": {
-
-			"should return the correct op name; $min": function testOpName(){
-				var acc = createAccumulator();
-				assert.equal(acc.getOpName(), "$min");
-			}
-
-		},
-
-		"#processInternal()": {
-
-			"The accumulator evaluates no documents": function none() {
-				// The accumulator returns no value in this case.
-				var acc = createAccumulator();
-				assert.ok(!acc.getValue());
-			},
-
-			"The accumulator evaluates one document and retains its value": function one() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				assert.strictEqual(acc.getValue(), 5);
-			},
-
-			"The accumulator evaluates one document with the field missing retains undefined": function missing() {
-				var acc = createAccumulator();
-				acc.processInternal();
-				assert.strictEqual(acc.getValue(), undefined);
-			},
-
-			"The accumulator evaluates two documents and retains the minimum": function two() {
-				var acc = createAccumulator();
-				acc.processInternal(5);
-				acc.processInternal(7);
-				assert.strictEqual(acc.getValue(), 5);
-			},
-
-			"The accumulator evaluates two documents and retains the undefined value in the last": function lastMissing() {
-				var acc = createAccumulator();
-				acc.processInternal(7);
-				acc.processInternal();
-				assert.strictEqual(acc.getValue(), undefined);
-			}
-		}
-
-	}
-
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 206 - 0
test/lib/pipeline/accumulators/MinMaxAccumulator.js

@@ -0,0 +1,206 @@
+"use strict";
+var assert = require("assert"),
+	MinMaxAccumulator = require("../../../../lib/pipeline/accumulators/MinMaxAccumulator");
+
+// 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.MinMaxAccumulator = {
+
+	".constructor()": {
+
+		"should create instance of Accumulator": function() {
+			assert(MinMaxAccumulator.createMax() instanceof MinMaxAccumulator);
+		},
+
+		"should throw error if called without args": function() {
+			assert.throws(function() {
+				new MinMaxAccumulator();
+			});
+		},
+
+		"should create instance of Accumulator if called with valid sense": function() {
+			new MinMaxAccumulator(-1);
+			new MinMaxAccumulator(1);
+		},
+
+		"should throw error if called with invalid sense": function() {
+			assert.throws(function() {
+				new MinMaxAccumulator(0);
+			});
+		},
+
+	},
+
+	".createMin()": {
+
+		"should return an instance of the accumulator": function() {
+			var acc = MinMaxAccumulator.createMin();
+			assert(acc instanceof MinMaxAccumulator);
+			assert.strictEqual(acc._sense, 1);
+		},
+
+	},
+
+	".createMax()": {
+
+		"should return an instance of the accumulator": function() {
+			var acc = MinMaxAccumulator.createMax();
+			assert(acc instanceof MinMaxAccumulator);
+			assert.strictEqual(acc._sense, -1);
+		},
+
+	},
+
+	"#process()": {
+
+		"Min": {
+
+			"should return undefined if no inputs evaluated": function testNone() {
+				var acc = MinMaxAccumulator.createMin();
+				assert.strictEqual(acc.getValue(), undefined);
+			},
+
+			"should return value for one input": function testOne() {
+				var acc = MinMaxAccumulator.createMin();
+				acc.process(5);
+				assert.strictEqual(acc.getValue(), 5);
+			},
+
+			"should return missing for one missing input": function testMissing() {
+				var acc = MinMaxAccumulator.createMin();
+				acc.process();
+				assert.strictEqual(acc.getValue(), undefined);
+			},
+
+			"should return minimum of two inputs": function testTwo() {
+				var acc = MinMaxAccumulator.createMin();
+				acc.process(5);
+				acc.process(7);
+				assert.strictEqual(acc.getValue(), 5);
+			},
+
+			"should return minimum of two inputs (ignoring undefined once found)": function testLastMissing() {
+				var acc = MinMaxAccumulator.createMin();
+				acc.process(7);
+				acc.process(undefined);
+				assert.strictEqual(acc.getValue(), 7);
+			},
+
+		},
+
+		"Max": {
+
+			"should return undefined if no inputs evaluated": function testNone() {
+				var acc = MinMaxAccumulator.createMax();
+				assert.strictEqual(acc.getValue(), undefined);
+			},
+
+			"should return value for one input": function testOne() {
+				var acc = MinMaxAccumulator.createMax();
+				acc.process(5);
+				assert.strictEqual(acc.getValue(), 5);
+			},
+
+			"should return missing for one missing input": function testMissing() {
+				var acc = MinMaxAccumulator.createMax();
+				acc.process();
+				assert.strictEqual(acc.getValue(), undefined);
+			},
+
+			"should return maximum of two inputs": function testTwo() {
+				var acc = MinMaxAccumulator.createMax();
+				acc.process(5);
+				acc.process(7);
+				assert.strictEqual(acc.getValue(), 7);
+			},
+
+			"should return maximum of two inputs (ignoring undefined once found)": function testLastMissing() {
+				var acc = MinMaxAccumulator.createMax();
+				acc.process(7);
+				acc.process(undefined);
+				assert.strictEqual(acc.getValue(), 7);
+			},
+
+		},
+
+	},
+
+	"#getValue()": {
+
+		"Min": {
+
+			"should get value the same for shard and router": function() {
+				var acc = MinMaxAccumulator.createMin();
+				assert.strictEqual(acc.getValue(false), acc.getValue(true));
+				acc.process(123);
+				assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			},
+
+		},
+
+		"Max": {
+
+			"should get value the same for shard and router": function() {
+				var acc = MinMaxAccumulator.createMax();
+				assert.strictEqual(acc.getValue(false), acc.getValue(true));
+				acc.process(123);
+				assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			},
+
+		},
+
+	},
+
+	"#reset()": {
+
+		"Min": {
+
+			"should reset to missing": function() {
+				var acc = MinMaxAccumulator.createMin();
+				assert.strictEqual(acc.getValue(), undefined);
+				acc.process(123);
+				assert.notEqual(acc.getValue(), undefined);
+				acc.reset();
+				assert.strictEqual(acc.getValue(), undefined);
+				assert.strictEqual(acc.getValue(true), undefined);
+			},
+
+		},
+
+		"Max": {
+
+			"should reset to missing": function() {
+				var acc = MinMaxAccumulator.createMax();
+				assert.strictEqual(acc.getValue(), undefined);
+				acc.process(123);
+				assert.notEqual(acc.getValue(), undefined);
+				acc.reset();
+				assert.strictEqual(acc.getValue(), undefined);
+				assert.strictEqual(acc.getValue(true), undefined);
+			},
+
+		},
+
+	},
+
+	"#getOpName()": {
+
+		"Min": {
+
+			"should return the correct op name; $min": function() {
+				assert.equal(MinMaxAccumulator.createMin().getOpName(), "$min");
+			},
+
+		},
+		"Max":{
+
+			"should return the correct op name; $max": function() {
+				assert.equal(MinMaxAccumulator.createMax().getOpName(), "$max");
+			},
+
+		},
+
+	},
+
+};

+ 97 - 77
test/lib/pipeline/accumulators/PushAccumulator.js

@@ -2,99 +2,119 @@
 var assert = require("assert"),
 	PushAccumulator = require("../../../../lib/pipeline/accumulators/PushAccumulator");
 
+// 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));
 
-function createAccumulator(){
-	return new PushAccumulator();
-}
 
-module.exports = {
+exports.PushAccumulator = {
 
-	"PushAccumulator": {
+	".constructor()": {
 
-		"constructor()": {
+		"should create instance of accumulator": function() {
+			assert(new PushAccumulator() instanceof PushAccumulator);
+		},
+
+		"should throw error if called with args": function() {
+			assert.throws(function() {
+				new PushAccumulator(123);
+			});
+		},
+
+	},
+
+	".create()": {
+
+		"should return an instance of the accumulator": function() {
+			assert(PushAccumulator.create() instanceof PushAccumulator);
+		},
+
+	},
+
+	"#process()": {
+
+		"should return empty array if no inputs evaluated": function() {
+			var acc = PushAccumulator.create();
+			assert.deepEqual(acc.getValue(), []);
+		},
+
+		"should return array of one value for one input": function() {
+			var acc = PushAccumulator.create();
+			acc.process(1);
+			assert.deepEqual(acc.getValue(), [1]);
+		},
+
+		"should return array of two values for two inputs": function() {
+			var acc = PushAccumulator.create();
+			acc.process(1);
+			acc.process(2);
+			assert.deepEqual(acc.getValue(), [1,2]);
+		},
+
+		"should return array of two values for two inputs (including null)": function() {
+			var acc = PushAccumulator.create();
+			acc.process(1);
+			acc.process(null);
+			assert.deepEqual(acc.getValue(), [1, null]);
+		},
+
+		"should return array of one value for two inputs if one is undefined": function() {
+			var acc = PushAccumulator.create();
+			acc.process(1);
+			acc.process(undefined);
+			assert.deepEqual(acc.getValue(), [1]);
+		},
+
+		"should return array of two values from two separate mergeable inputs": function() {
+			var acc = PushAccumulator.create();
+			acc.process([1], true);
+			acc.process([0], true);
+			assert.deepEqual(acc.getValue(), [1, 0]);
+		},
+
+		"should throw error if merging non-array": function() {
+			var acc = PushAccumulator.create();
+			assert.throws(function() {
+				acc.process(0, true);
+			});
+			assert.throws(function() {
+				acc.process("foo", true);
+			});
+		},
+
+	},
 
-			"should not throw Error when constructing without args": function testConstructor(){
-				assert.doesNotThrow(function(){
-					new PushAccumulator();
-				});
-			}
+	"#getValue()": {
 
+		"should get value the same for shard and router": function() {
+			var acc = PushAccumulator.create();
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			acc.process(123);
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
 		},
 
-		"#getOpName()": {
+	},
 
-			"should return the correct op name; $push": function testOpName(){
-				assert.strictEqual(new PushAccumulator().getOpName(), "$push");
-			}
+	"#reset()": {
 
+		"should reset to empty array": function() {
+			var acc = PushAccumulator.create();
+			assert.deepEqual(acc.getValue(), []);
+			acc.process(123);
+			assert.notDeepEqual(acc.getValue(), []);
+			acc.reset();
+			assert.deepEqual(acc.getValue(), []);
+			assert.deepEqual(acc.getValue(true), []);
 		},
 
-		"#getFactory()": {
+	},
 
-			"should return the constructor for this class": function factoryIsConstructor(){
-				assert.strictEqual(new PushAccumulator().getFactory(), PushAccumulator);
-			}
+	"#getOpName()": {
 
+		"should return the correct op name; $push": function(){
+			assert.strictEqual(new PushAccumulator().getOpName(), "$push");
 		},
 
-		"#processInternal()": {
-
-			"should processInternal no documents and return []": function testprocessInternal_None(){
-				var accumulator = createAccumulator();
-				assert.deepEqual(accumulator.getValue(), []);
-			},
-
-			"should processInternal a 1 and return [1]": function testprocessInternal_One(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				assert.deepEqual(accumulator.getValue(), [1]);
-			},
-
-			"should processInternal a 1 and a 2 and return [1,2]": function testprocessInternal_OneTwo(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				accumulator.processInternal(2);
-				assert.deepEqual(accumulator.getValue(), [1,2]);
-			},
-
-			"should processInternal a 1 and a null and return [1,null]": function testprocessInternal_OneNull(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				accumulator.processInternal(null);
-				assert.deepEqual(accumulator.getValue(), [1, null]);
-			},
-
-			"should processInternal a 1 and an undefined and return [1]": function testprocessInternal_OneUndefined(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				accumulator.processInternal(undefined);
-				assert.deepEqual(accumulator.getValue(), [1]);
-			},
-
-			"should processInternal a 1 and a 0 and return [1,0]": function testprocessInternal_OneZero(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				accumulator.processInternal(0);
-				assert.deepEqual(accumulator.getValue(), [1, 0]);
-			},
-
-			"should processInternal a 1 and a [0] and return [1,0]": function testprocessInternal_OneArrayZeroMerging(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				accumulator.processInternal([0], true);
-				assert.deepEqual(accumulator.getValue(), [1, 0]);
-			},
-
-			"should processInternal a 1 and a 0 and throw an error if merging": function testprocessInternal_OneZeroMerging(){
-				var accumulator = createAccumulator();
-				accumulator.processInternal(1);
-				assert.throws(function() {
-					accumulator.processInternal(0, true);
-				});
-			}
-		}
-
-	}
+	},
 
 };
 

+ 240 - 78
test/lib/pipeline/accumulators/SumAccumulator.js

@@ -2,113 +2,275 @@
 var assert = require("assert"),
 	SumAccumulator = require("../../../../lib/pipeline/accumulators/SumAccumulator");
 
+// 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));
 
-function createAccumulator(){
-	return new SumAccumulator();
-}
+exports.SumAccumulator = {
 
+	".constructor()": {
 
-module.exports = {
+		"should create instance of Accumulator": function() {
+			assert(new SumAccumulator() instanceof SumAccumulator);
+		},
+
+		"should throw error if called with args": function() {
+			assert.throws(function() {
+				new SumAccumulator(123);
+			});
+		},
+
+	},
+
+	".create()": {
+
+		"should return an instance of the accumulator": function() {
+			assert(SumAccumulator.create() instanceof SumAccumulator);
+		},
+
+	},
+
+	"#process()": {
+
+		"should return 0 if no inputs evaluated": function testNone() {
+			var acc = SumAccumulator.create();
+			assert.strictEqual(acc.getValue(), 0);
+		},
+
+		"should return value for one int input": function testOneInt() {
+			var acc = SumAccumulator.create();
+			acc.process(5);
+			assert.strictEqual(acc.getValue(), 5);
+		},
+
+		"should return value for one long input": function testOneLong() {
+			var acc = SumAccumulator.create();
+			acc.process(6e24);
+			assert.strictEqual(acc.getValue(), 6e24);
+		},
+
+		"should return value for one large long input": function testOneLargeLong() {
+			var acc = SumAccumulator.create();
+			acc.process(6e42);
+			assert.strictEqual(acc.getValue(), 6e42);
+		},
+
+		"should return value for one double input": function testOneDouble() {
+			var acc = SumAccumulator.create();
+			acc.process(7.0);
+			assert.strictEqual(acc.getValue(), 7.0);
+		},
+
+		"should return value for one fractional double input": function testNanDouble() {
+			var acc = SumAccumulator.create();
+			acc.process(NaN);
+			assert.notEqual(acc.getValue(), acc.getValue()); // NaN is unequal to itself.
+		},
+
+		beforeEach: function() { // used in the tests below
+			this.getSumValueFor = function(first, second) { // kind of like TwoOperandBase
+				var acc = SumAccumulator.create();
+				for (var i = 0, l = arguments.length; i < l; i++) {
+					acc.process(arguments[i]);
+				}
+				return acc.getValue();
+			};
+		},
+
+		"should return sum for two ints": function testIntInt() {
+			var summand1 = 4,
+				summand2 = 5,
+				expected = 9;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for two ints (overflow)": function testIntIntOverflow() {
+			var summand1 = 32767,
+				summand2 = 10,
+				expected = 32767 + 10;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for two ints (negative overflow)": function testIntIntNegativeOverflow() {
+			var summand1 = 32767,
+				summand2 = -10,
+				expected = 32767 + -10;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-	"SumAccumulator": {
+		"should return sum for int and long": function testIntLong() {
+			var summand1 = 4,
+				summand2 = 5e24,
+				expected = 4 + 5e24;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-		"constructor()": {
+		"should return sum for max int and long (no int overflow)": function testIntLongNoIntOverflow() {
+			var summand1 = 32767,
+				summand2 = 1e24,
+				expected = 32767 + 1e24;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for int and max long (long overflow)": function testIntLongLongOverflow() {
+			var summand1 = 1,
+				summand2 = 9223372036854775807,
+				expected = 1 + 9223372036854775807;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for long and long": function testLongLong() {
+			var summand1 = 4e24,
+				summand2 = 5e24,
+				expected = 4e24 + 5e24;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for max long and max long (overflow)": function testLongLongOverflow() {
+			var summand1 = 9223372036854775807,
+				summand2 = 9223372036854775807,
+				expected = 9223372036854775807 + 9223372036854775807;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
+
+		"should return sum for int and double": function testIntDouble() {
+			var summand1 = 4,
+				summand2 = 5.5,
+				expected = 9.5;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-			"should not throw Error when constructing without args": function testConstructor(){
-				assert.doesNotThrow(function(){
-					new SumAccumulator();
-				});
-			}
+		"should return sum for int and NaN as NaN": function testIntNanDouble() {
+			var summand1 = 4,
+				summand2 = NaN,
+				expected = NaN;
+			assert(isNaN(this.getSumValueFor(summand1, summand2)));
+			assert(isNaN(this.getSumValueFor(summand2, summand1)));
+		},
 
+		"should return sum for int and double (no int overflow)": function testIntDoubleNoIntOverflow() {
+			var summand1 = 32767,
+				summand2 = 1.0,
+				expected = 32767 + 1.0;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
 		},
 
-		"#getOpName()": {
+		"should return sum for long and double": function testLongDouble() {
+			var summand1 = 4e24,
+				summand2 = 5.5,
+				expected = 4e24 + 5.5;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-			"should return the correct op name; $sum": function testOpName(){
-				assert.strictEqual(new SumAccumulator().getOpName(), "$sum");
-			}
+		"should return sum for max long and double (no long overflow)": function testLongDoubleNoLongOverflow() {
+			var summand1 = 9223372036854775807,
+				summand2 = 1.0,
+				expected = 9223372036854775807 + 1.0;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
+		"should return sum for double and double": function testDoubleDouble() {
+			var summand1 = 2.5,
+				summand2 = 5.5,
+				expected = 8.0;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
 		},
 
-		"#processInternal()": {
+		"should return sum for double and double (overflow)": function testDoubleDoubleOverflow() {
+			var summand1 = Number.MAX_VALUE,
+				summand2 = Number.MAX_VALUE,
+				expected = Infinity;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-			"should evaluate no documents": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				assert.strictEqual(sumAccumulator.getValue(), 0);
-			},
+		"should return sum for int and long and double": function testIntLongDouble() {
+			assert.strictEqual(this.getSumValueFor(5, 99, 0.2), 104.2);
+		},
 
-			"should evaluate one document with a field that is NaN": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(Number("foo"));
-				// NaN is unequal to itself
-				assert.notStrictEqual(sumAccumulator.getValue(), sumAccumulator.getValue());
-			},
+		"should return sum for a negative value": function testNegative() {
+			var summand1 = 5,
+				summand2 = -8.8,
+				expected = 5 - 8.8;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
+		"should return sum for long and negative int": function testLongIntNegative() {
+			var summand1 = 5e24,
+				summand2 = -6,
+				expected = 5e24 - 6;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-			"should evaluate one document and sum it's value": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(5);
-				assert.strictEqual(sumAccumulator.getValue(), 5);
+		"should return sum for int and null": function testIntNull() {
+			var summand1 = 5,
+				summand2 = null,
+				expected = 5;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
-			},
+		"should return sum for int and undefined": function testIntUndefined() {
+			var summand1 = 9,
+				summand2, // = undefined,
+				expected = 9;
+			assert.strictEqual(this.getSumValueFor(summand1, summand2), expected);
+			assert.strictEqual(this.getSumValueFor(summand2, summand1), expected);
+		},
 
+		"should return sum for long long max and long long max and 1": function testNoOverflowBeforeDouble() {
+			var actual = this.getSumValueFor(9223372036854775807, 9223372036854775807, 1.0),
+				expected = 9223372036854775807 + 9223372036854775807;
+			assert.strictEqual(actual, expected);
+		},
 
-			"should evaluate and sum two ints": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(5);
-				sumAccumulator.processInternal(7);
-				assert.strictEqual(sumAccumulator.getValue(), 12);
-			},
+	},
 
-			"should evaluate and sum two ints overflow": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(Number.MAX_VALUE);
-				sumAccumulator.processInternal(Number.MAX_VALUE);
-				assert.strictEqual(Number.isFinite(sumAccumulator.getValue()), false);
-			},
+	"#getValue()": {
 
+		"should get value the same for shard and router": function() {
+			var acc = SumAccumulator.create();
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+			acc.process(123);
+			assert.strictEqual(acc.getValue(false), acc.getValue(true));
+		},
 
-			"should evaluate and sum two negative ints": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(-5);
-				sumAccumulator.processInternal(-7);
-				assert.strictEqual(sumAccumulator.getValue(), -12);
-			},
+	},
 
-//TODO Not sure how to do this in Javascript
-//			"should evaluate and sum two negative ints overflow": function testStuff(){
-//				var sumAccumulator = createAccumulator();
-//				sumAccumulator.processInternal({b:Number.MIN_VALUE});
-//				sumAccumulator.processInternal({b:7});
-//				assert.strictEqual(sumAccumulator.getValue(), Number.MAX_VALUE);
-//			},
-//
+	"#reset()": {
 
-			"should evaluate and sum int and float": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(8.5);
-				sumAccumulator.processInternal(7);
-				assert.strictEqual(sumAccumulator.getValue(), 15.5);
-			},
+		"should reset to 0": function() {
+			var acc = SumAccumulator.create();
+			assert.strictEqual(acc.getValue(), 0);
+			acc.process(123);
+			assert.notEqual(acc.getValue(), 0);
+			acc.reset();
+			assert.strictEqual(acc.getValue(), 0);
+			assert.strictEqual(acc.getValue(true), 0);
+		},
 
-			"should evaluate and sum one Number and a NaN sum to NaN": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(8);
-				sumAccumulator.processInternal(Number("bar"));
-				// NaN is unequal to itself
-				assert.notStrictEqual(sumAccumulator.getValue(), sumAccumulator.getValue());
-			},
+	},
 
-			"should evaluate and sum a null value to 0": function testStuff(){
-				var sumAccumulator = createAccumulator();
-				sumAccumulator.processInternal(null);
-				assert.strictEqual(sumAccumulator.getValue(), 0);
-			}
+	"#getOpName()": {
 
+		"should return the correct op name; $sum": function() {
+			assert.equal(SumAccumulator.create().getOpName(), "$sum");
 		}
 
-	}
+	},
 
 };
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 0 - 131
test/lib/pipeline/matcher/AllElemMatchOp.js

@@ -1,131 +0,0 @@
-"use strict";
-var assert = require("assert"),
-	EqualityMatchExpression = require("../../../../lib/pipeline/matcher/EqualityMatchExpression.js"),
-	ElemMatchObjectMatchExpression = require("../../../../lib/pipeline/matcher/ElemMatchObjectMatchExpression.js"),
-	ElemMatchValueMatchExpression = require("../../../../lib/pipeline/matcher/ElemMatchValueMatchExpression.js"),
-	AndMatchExpression = require("../../../../lib/pipeline/matcher/AndMatchExpression.js"),
-	LTMatchExpression = require("../../../../lib/pipeline/matcher/LTMatchExpression.js"),
-	GTMatchExpression = require("../../../../lib/pipeline/matcher/GTMatchExpression.js"),
-	AllElemMatchOp = require("../../../../lib/pipeline/matcher/AllElemMatchOp.js");
-
-
-module.exports = {
-	"AllElemMatchOp": {
-		"Should match an element": function() {
-			var baseOperanda1={"a":1},
-				eqa1 = new EqualityMatchExpression();
-
-			assert.strictEqual(eqa1.init("a", baseOperanda1.a).code, 'OK');
-
-			var baseOperandb1={"b":1},
-				eqb1 = new EqualityMatchExpression(),
-				and1 = new AndMatchExpression(),
-				elemMatch1 = new ElemMatchObjectMatchExpression();
-
-			assert.strictEqual(eqb1.init("b", baseOperandb1.b).code, 'OK');
-
-			and1.add(eqa1);
-			and1.add(eqb1);
-			// and1 = { a : 1, b : 1 }
-
-			elemMatch1.init("x", and1);
-			// elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } }
-
-			var baseOperanda2={"a":2},
-				eqa2 = new EqualityMatchExpression();
-
-			assert.strictEqual(eqa2.init("a", baseOperanda2.a).code, 'OK');
-
-			var baseOperandb2={"b":2},
-				eqb2 = new EqualityMatchExpression(),
-				and2 = new AndMatchExpression(),
-				elemMatch2 = new ElemMatchObjectMatchExpression(),
-				op = new AllElemMatchOp();
-
-			assert.strictEqual(eqb2.init("b", baseOperandb2.b).code, 'OK');
-
-			and2.add(eqa2);
-			and2.add(eqb2);
-
-			elemMatch2.init("x", and2);
-			// elemMatch2 = { x : { $elemMatch : { a : 2, b : 2 } } }
-
-			op.init("");
-			op.add(elemMatch1);
-			op.add(elemMatch2);
-
-			var nonArray={"x":4},
-				emptyArray={"x":[]},
-				nonObjArray={"x":[4]},
-				singleObjMatch={"x":[{"a":1, "b":1}]},
-				otherObjMatch={"x":[{"a":2, "b":2}]},
-				bothObjMatch={"x":[{"a":1, "b":1}, {"a":2, "b":2}]},
-				noObjMatch={"x":[{"a":1, "b":2}, {"a":2, "b":1}]};
-
-			assert.ok(!op.matchesSingleElement(nonArray.x));
-			assert.ok(!op.matchesSingleElement(emptyArray.x));
-			assert.ok(!op.matchesSingleElement(nonObjArray.x));
-			assert.ok(!op.matchesSingleElement(singleObjMatch.x));
-			assert.ok(!op.matchesSingleElement(otherObjMatch.x));
-			assert.ok(op.matchesSingleElement(bothObjMatch.x));
-			assert.ok(!op.matchesSingleElement(noObjMatch.x));
-		},
-
-		"Should match things": function() {
-			var baseOperandgt1={"$gt":1},
-				gt1 = new GTMatchExpression();
-
-			assert.strictEqual(gt1.init("", baseOperandgt1.$gt).code, 'OK');
-
-			var baseOperandlt1={"$lt":10},
-				lt1 = new LTMatchExpression(),
-				elemMatch1 = new ElemMatchValueMatchExpression();
-
-			assert.strictEqual(lt1.init("", baseOperandlt1.$lt).code, 'OK');
-
-			elemMatch1.init("x");
-			elemMatch1.add(gt1);
-			elemMatch1.add(lt1);
-
-			var baseOperandgt2={"$gt":101},
-				gt2 = new GTMatchExpression();
-
-			assert.strictEqual(gt2.init("", baseOperandgt2.$gt).code, 'OK');
-
-			var baseOperandlt2={"$lt":110},
-				lt2 = new LTMatchExpression(),
-				elemMatch2 = new ElemMatchValueMatchExpression(),
-				op = new AllElemMatchOp();
-
-			assert.strictEqual(lt2.init("", baseOperandlt2.$lt).code, 'OK');
-
-			elemMatch2.init("x");
-			elemMatch2.add(gt2);
-			elemMatch2.add(lt2);
-
-			op.init("x");
-			op.add(elemMatch1);
-			op.add(elemMatch2);
-
-
-			var nonArray={"x":4},
-				emptyArray={"x":[]},
-				nonNumberArray={"x":["q"]},
-				singleMatch={"x":[5]},
-				otherMatch={"x":[105]},
-				bothMatch={"x":[5,105]},
-				neitherMatch={"x":[0,200]};
-
-			assert.ok(!op.matches(nonArray, null));
-			assert.ok(!op.matches(emptyArray, null));
-			assert.ok(!op.matches(nonNumberArray, null));
-			assert.ok(!op.matches(singleMatch, null));
-			assert.ok(!op.matches(otherMatch, null));
-			assert.ok(op.matches(bothMatch, null));
-			assert.ok(!op.matches(neitherMatch, null));
-		},
-	}
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 128 - 0
test/lib/pipeline/matcher/AllElemMatchOp_test.js

@@ -0,0 +1,128 @@
+"use strict";
+var assert = require("assert"),
+	EqualityMatchExpression = require("../../../../lib/pipeline/matcher/EqualityMatchExpression.js"),
+	ElemMatchObjectMatchExpression = require("../../../../lib/pipeline/matcher/ElemMatchObjectMatchExpression.js"),
+	ElemMatchValueMatchExpression = require("../../../../lib/pipeline/matcher/ElemMatchValueMatchExpression.js"),
+	AndMatchExpression = require("../../../../lib/pipeline/matcher/AndMatchExpression.js"),
+	LTMatchExpression = require("../../../../lib/pipeline/matcher/LTMatchExpression.js"),
+	GTMatchExpression = require("../../../../lib/pipeline/matcher/GTMatchExpression.js"),
+	AllElemMatchOp = require("../../../../lib/pipeline/matcher/AllElemMatchOp.js");
+
+// 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.AllElemMatchOp = {
+	"Should match an element": function() {
+		var baseOperanda1={"a":1},
+			eqa1 = new EqualityMatchExpression();
+
+		assert.strictEqual(eqa1.init("a", baseOperanda1.a).code, 'OK');
+
+		var baseOperandb1={"b":1},
+			eqb1 = new EqualityMatchExpression(),
+			and1 = new AndMatchExpression(),
+			elemMatch1 = new ElemMatchObjectMatchExpression();
+
+		assert.strictEqual(eqb1.init("b", baseOperandb1.b).code, 'OK');
+
+		and1.add(eqa1);
+		and1.add(eqb1);
+		// and1 = { a : 1, b : 1 }
+
+		elemMatch1.init("x", and1);
+		// elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } }
+
+		var baseOperanda2={"a":2},
+			eqa2 = new EqualityMatchExpression();
+
+		assert.strictEqual(eqa2.init("a", baseOperanda2.a).code, 'OK');
+
+		var baseOperandb2={"b":2},
+			eqb2 = new EqualityMatchExpression(),
+			and2 = new AndMatchExpression(),
+			elemMatch2 = new ElemMatchObjectMatchExpression(),
+			op = new AllElemMatchOp();
+
+		assert.strictEqual(eqb2.init("b", baseOperandb2.b).code, 'OK');
+
+		and2.add(eqa2);
+		and2.add(eqb2);
+
+		elemMatch2.init("x", and2);
+		// elemMatch2 = { x : { $elemMatch : { a : 2, b : 2 } } }
+
+		op.init("");
+		op.add(elemMatch1);
+		op.add(elemMatch2);
+
+		var nonArray={"x":4},
+			emptyArray={"x":[]},
+			nonObjArray={"x":[4]},
+			singleObjMatch={"x":[{"a":1, "b":1}]},
+			otherObjMatch={"x":[{"a":2, "b":2}]},
+			bothObjMatch={"x":[{"a":1, "b":1}, {"a":2, "b":2}]},
+			noObjMatch={"x":[{"a":1, "b":2}, {"a":2, "b":1}]};
+
+		assert.ok(!op.matchesSingleElement(nonArray.x));
+		assert.ok(!op.matchesSingleElement(emptyArray.x));
+		assert.ok(!op.matchesSingleElement(nonObjArray.x));
+		assert.ok(!op.matchesSingleElement(singleObjMatch.x));
+		assert.ok(!op.matchesSingleElement(otherObjMatch.x));
+		assert.ok(op.matchesSingleElement(bothObjMatch.x));
+		assert.ok(!op.matchesSingleElement(noObjMatch.x));
+	},
+
+	"Should match things": function() {
+		var baseOperandgt1={"$gt":1},
+			gt1 = new GTMatchExpression();
+
+		assert.strictEqual(gt1.init("", baseOperandgt1.$gt).code, 'OK');
+
+		var baseOperandlt1={"$lt":10},
+			lt1 = new LTMatchExpression(),
+			elemMatch1 = new ElemMatchValueMatchExpression();
+
+		assert.strictEqual(lt1.init("", baseOperandlt1.$lt).code, 'OK');
+
+		elemMatch1.init("x");
+		elemMatch1.add(gt1);
+		elemMatch1.add(lt1);
+
+		var baseOperandgt2={"$gt":101},
+			gt2 = new GTMatchExpression();
+
+		assert.strictEqual(gt2.init("", baseOperandgt2.$gt).code, 'OK');
+
+		var baseOperandlt2={"$lt":110},
+			lt2 = new LTMatchExpression(),
+			elemMatch2 = new ElemMatchValueMatchExpression(),
+			op = new AllElemMatchOp();
+
+		assert.strictEqual(lt2.init("", baseOperandlt2.$lt).code, 'OK');
+
+		elemMatch2.init("x");
+		elemMatch2.add(gt2);
+		elemMatch2.add(lt2);
+
+		op.init("x");
+		op.add(elemMatch1);
+		op.add(elemMatch2);
+
+
+		var nonArray={"x":4},
+			emptyArray={"x":[]},
+			nonNumberArray={"x":["q"]},
+			singleMatch={"x":[5]},
+			otherMatch={"x":[105]},
+			bothMatch={"x":[5,105]},
+			neitherMatch={"x":[0,200]};
+
+		assert.ok(!op.matches(nonArray, null));
+		assert.ok(!op.matches(emptyArray, null));
+		assert.ok(!op.matches(nonNumberArray, null));
+		assert.ok(!op.matches(singleMatch, null));
+		assert.ok(!op.matches(otherMatch, null));
+		assert.ok(op.matches(bothMatch, null));
+		assert.ok(!op.matches(neitherMatch, null));
+	}
+};

+ 8 - 8
test/lib/pipeline/matcher/AndMatchExpression.js

@@ -60,11 +60,11 @@ module.exports = {
 			andOp.add(sub2);
 			andOp.add(sub3);
 
-			assert.ok(andOp.matchesBSON({"a":5, "b":6}, null));
-			assert.ok(!andOp.matchesBSON({"a":5}, null));
-			assert.ok(!andOp.matchesBSON({"b":6}, null ));
-			assert.ok(!andOp.matchesBSON({"a":1, "b":6}, null));
-			assert.ok(!andOp.matchesBSON({"a":10, "b":6}, null));
+			assert.ok(andOp.matchesJSON({"a":5, "b":6}, null));
+			assert.ok(!andOp.matchesJSON({"a":5}, null));
+			assert.ok(!andOp.matchesJSON({"b":6}, null ));
+			assert.ok(!andOp.matchesJSON({"a":1, "b":6}, null));
+			assert.ok(!andOp.matchesJSON({"a":10, "b":6}, null));
 		},
 		"Should have an elemMatchKey": function(){
 			var baseOperand1 = {"a":1},
@@ -81,11 +81,11 @@ module.exports = {
 			andOp.add(sub2);
 
 			details.requestElemMatchKey();
-			assert.ok(!andOp.matchesBSON({"a":[1]}, details));
+			assert.ok(!andOp.matchesJSON({"a":[1]}, details));
 			assert.ok(!details.hasElemMatchKey());
-			assert.ok(!andOp.matchesBSON({"b":[2]}, details));
+			assert.ok(!andOp.matchesJSON({"b":[2]}, details));
 			assert.ok(!details.hasElemMatchKey());
-			assert.ok(andOp.matchesBSON({"a":[1], "b":[1, 2]}, details));
+			assert.ok(andOp.matchesJSON({"a":[1], "b":[1, 2]}, details));
 			assert.ok(details.hasElemMatchKey());
 			// The elem match key for the second $and clause is recorded.
 			assert.strictEqual("1", details.elemMatchKey());

+ 0 - 50
test/lib/pipeline/matcher/ComparisonMatchExpression.js

@@ -1,50 +0,0 @@
-"use strict";
-var assert = require("assert"),
-	ComparisonMatchExpression = require("../../../../lib/pipeline/matcher/ComparisonMatchExpression");
-
-
-module.exports = {
-	"ComparisonMatchExpression": {
-
-		"Should properly initialize with an empty path and a number": function (){
-			var e = new ComparisonMatchExpression();
-			e._matchType = 'LT';
-			assert.strictEqual(e.init('', 5 ).code,'OK');
-		},
-		"Should not initialize when given an undefined rhs": function() {
-			var e = new ComparisonMatchExpression();
-			assert.strictEqual(e.init('',5).code,'BAD_VALUE');
-			e._matchType = 'LT';
-			assert.strictEqual(e.init('',{}).code,'BAD_VALUE');	
-			assert.strictEqual(e.init('',undefined).code,'BAD_VALUE');
-			assert.strictEqual(e.init('',{}).code,'BAD_VALUE');
-		},
-		"Should match numbers with GTE": function (){
-			var e = new ComparisonMatchExpression();
-			e._matchType = 'GTE';
-			assert.strictEqual(e.init('',5).code,'OK');
-			assert.ok(e.matchesSingleElement(6), "6 ≥ 5");
-			assert.ok(e.matchesSingleElement(5), "5 ≥ 5");
-			assert.ok(!e.matchesSingleElement(4), "4 ≥ 5");
-			assert.ok(!e.matchesSingleElement('foo'), "5 ≥ 'foo'");
-		},
-		"Should match with simple paths and GTE": function(){
-			var e = new ComparisonMatchExpression();
-			e._matchType = 'GTE';
-			assert.strictEqual(e.init('a', 5).code,'OK');
-			assert.ok(e.matches({'a':6}));
-		},
-		"Should match arrays with GTE": function (){
-			var e = new ComparisonMatchExpression();
-			e._matchType = 'GTE';
-			assert.strictEqual(e.init('a',5).code,'OK');
-			assert.ok(e.matches({'a':[6,10]}),'[6,10] ≥ 5');
-			assert.ok(e.matches({'a':[4,5.5]}), '[4,5.5] ≥ 5');
-			assert.ok(!e.matches({'a':[1,2]}),'[1,2] ≥ 5');
-			assert.ok(e.matches({'a':[1,10]}),'[1,10] ≥ 5');
-		}
-	}
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 110 - 0
test/lib/pipeline/matcher/ComparisonMatchExpression_test.js

@@ -0,0 +1,110 @@
+"use strict";
+var assert = require("assert"),
+	bson = require("bson"),
+	MinKey = bson.BSONPure.MinKey,
+	MaxKey = bson.BSONPure.MaxKey,
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails"),
+	ComparisonMatchExpression = require("../../../../lib/pipeline/matcher/ComparisonMatchExpression");
+
+// 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.ComparisonMatchExpression = {
+
+	"should properly initialize with an empty path and a number": function () {
+		var e = new ComparisonMatchExpression('LT');
+		assert.strictEqual(e.init('',5).code,'OK');
+	},
+	"should not initialize when given an invalid operand": function() {
+		var e = new ComparisonMatchExpression('');
+		assert.strictEqual(e.init('',5).code, 'BAD_VALUE');
+	},
+	"should not initialize when given an undefined rhs": function() {
+		var e = new ComparisonMatchExpression();
+		assert.strictEqual(e.init('',5).code,'BAD_VALUE');
+		e._matchType = 'LT';
+		assert.strictEqual(e.init('',{}).code,'BAD_VALUE');
+		assert.strictEqual(e.init('',undefined).code,'BAD_VALUE');
+		assert.strictEqual(e.init('',{}).code,'BAD_VALUE');
+	},
+	"should match numbers with GTE": function () {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('',5).code,'OK');
+		assert.ok(e.matchesSingleElement(6),'6 ≥ 5');
+		assert.ok(e.matchesSingleElement(5),'5 ≥ 5');
+		assert.ok(!e.matchesSingleElement(4),'4 !≥ 5');
+		assert.ok(!e.matchesSingleElement('foo'),"'foo' !≥ 5");
+	},
+	"should match with simple paths and GTE": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a',5).code,'OK');
+		assert.ok(e.matches({'a':6}));
+	},
+	"should match array values with GTE": function () {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a',5).code,'OK');
+		assert.ok(e.matches({'a':[6,10]}),'[6,10] ≥ 5');
+		assert.ok(e.matches({'a':[4,5.5]}),'[4,5.5] ≥ 5');
+		assert.ok(!e.matches({'a':[1,2]}),'[1,2] !≥ 5');
+		assert.ok(e.matches({'a':[1,10]}),'[1,10] ≥ 5');
+	},
+	"should match entire arrays with GTE": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a',[5]).code,'OK');
+		assert.ok(!e.matches({'a':[4]}),'[4] !≥ [5]');
+		assert.ok(e.matches({'a':[5]}),'[5] !≥ [5]');
+		assert.ok(e.matches({'a':[6]}),'[6] !≥ [5]');
+		// documents current behavior
+		assert.ok(e.matches({'a':[[6]]}),'[[4]] ≥ [5]');
+		assert.ok(e.matches({'a':[[6]]}),'[[5]] ≥ [5]');
+		assert.ok(e.matches({'a':[[6]]}),'[[6]] ≥ [5]');
+	},
+	"should match null with GTE": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		e._matchType = 'GTE';
+		assert.strictEqual(e.init('a',null).code,'OK');
+		assert.ok(e.matches({}),'{} ≥ null');
+		assert.ok(e.matches({'a':null}),'null ≥ null');
+		assert.ok(!e.matches({'a':4}),'4 !≥ null');
+		assert.ok(e.matches({'b':null}),'non-existent field ≥ null');
+	},
+	"should match null in dotted paths with GTE": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a.b',null).code,'OK');
+		assert.ok(e.matches({}),'{} ≥ null');
+		assert.ok(e.matches({'a':null}),'{a:null} ≥ {a.b:null}');
+		assert.ok(e.matches({'a':4}),'{a:4} ≥ {a.b:null}');
+		assert.ok(e.matches({'a':{}}),'{a:{}} ≥ {a.b:null}');
+		assert.ok(e.matches({'a':[{'b':null}]}),'{a:[{b:null}]} ≥ {a.b:null}');
+		assert.ok(e.matches({'a':[{'a':4},{'b':4}]}),'{a:[{a:4},{b:4}]} ≥ {a.b:null}');
+		assert.ok(!e.matches({'a':[4]}),'{a:[4]} !≥ {a.b:null}');
+		assert.ok(!e.matches({'a':[{'b':4}]}),'{a:[{b:4}]} !≥ {a.b:null}');
+	},
+	"should match MinKeys": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a',new MinKey()).code,'OK');
+		assert.ok(e.matches({'a':new MinKey()}),'minKey ≥ minKey');
+		assert.ok(e.matches({'a':new MaxKey()}),'maxKey ≥ minKey');
+		assert.ok(e.matches({'a':4}),'4 ≥ minKey');
+	},
+	"should match MaxKeys": function() {
+		var e = new ComparisonMatchExpression('GTE');
+		assert.strictEqual(e.init('a',new MaxKey()).code,'OK');
+		assert.ok(e.matches({'a':new MaxKey()}),'maxKey ≥ maxKey');
+		assert.ok(!e.matches({'a':new MinKey()}),'minKey !≥ maxKey');
+		assert.ok(!e.matches({'a':4},null),'4 !≥ maxKey');
+	},
+	"should properly set match keys": function() {
+		var e = new ComparisonMatchExpression('GTE'),
+			d = new MatchDetails();
+		d.requestElemMatchKey();
+		assert.strictEqual(e.init('a',5).code,'OK');
+		assert.ok(!e.matchesJSON({'a':4},d),'4 !≥ 5');
+		assert(!d.hasElemMatchKey());
+		assert.ok(e.matchesJSON({'a':6},d),'6 ≥ 5');
+		assert(!d.hasElemMatchKey());
+		assert.ok(e.matchesJSON({'a':[2,6,5]},d),'[2,6,5] ≥ 5');
+		assert(d.hasElemMatchKey());
+		assert.strictEqual('1',d.elemMatchKey());
+	}
+};

+ 1 - 3
test/lib/pipeline/matcher/ElementPath.js

@@ -4,6 +4,7 @@ var assert = require("assert"),
 
 module.exports = {
 	"ElementPath": {
+		
 		"Should find the item at the path": function() {
 			var p = new ElementPath(),
 				doc = {"x":4, "a":5},
@@ -136,10 +137,7 @@ module.exports = {
 			};
 			p._matches(doc, null, checker);
 		}
-
-
 	}
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 48 - 24
test/lib/pipeline/matcher/EqualityMatchExpression.js

@@ -1,31 +1,16 @@
 "use strict";
 var assert = require("assert"),
 	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
-	EqualityMatchExpression = require("../../../../lib/pipeline/matcher/EqualityMatchExpression");
+	EqualityMatchExpression = require("../../../../lib/pipeline/matcher/EqualityMatchExpression"),
+	// TODO: replace the following with a real BSONTypes at some point
+	MinKey = new (function MinKey(){/*matcher does weird stuff with empty objects*/this.foo = 'bar';})(), // jshint ignore:line
+	MaxKey = new (function MaxKey(){/*matcher does weird stuff with empty objects*/this.foo = 'bar';})(); // jshint ignore:line
 
 
 module.exports = {
 
 	"EqualityMatchExpression": {
 
-		"should initialize equality and match numbers or numbers in arrays": function (){
-			var e = new EqualityMatchExpression();
-			var s = e.init('x', 5);
-			assert.strictEqual(s.code, 'OK');
-
-			assert.ok(e.matches({x:5}));
-			assert.ok(e.matches({x:[5]}));
-			assert.ok(e.matches({x:[1,5]}));
-			assert.ok(e.matches({x:[1,5,2]}));
-			assert.ok(e.matches({x:[5,2]}));
-
-			assert.ok(!e.matches({x:null}));
-			assert.ok(!e.matches({x:6}));
-			assert.ok(!e.matches({x:[4,2]}));
-			assert.ok(!e.matches({x:[[5]]}));
-		},
-
-//NOTE: from expression_leaf_test.cpp
 		"should match elements": function testMatchesElement(){
 			var operand = {a:5},
 				match = {a:5.0},
@@ -44,7 +29,7 @@ module.exports = {
 			var e = new EqualityMatchExpression();
 			var s = e.init('',{});
 
-			assert.strictEqual(s.code, 'BAD_VALUE');
+			assert.ok(s.code !== 'OK');
 		},
 		"should match a pathed number":function() {
 			var e = new EqualityMatchExpression();
@@ -82,12 +67,53 @@ module.exports = {
 		"should match null" : function() {
 			var e = new EqualityMatchExpression();
 			var s = e.init('a',null);
-		
+
 			assert.strictEqual(s.code, 'OK');
 			assert.ok( e.matches({}) );
 			assert.ok( e.matches({'a':null}) );
 			assert.ok( ! e.matches({'a':4}) );
+			assert.ok( e.matches({'b':4}) );
+		},
+    //// This test documents how the matcher currently works,
+    //// not necessarily how it should work ideally.
+		"should match nested nulls" : function(){
+			var e = new EqualityMatchExpression();
+			var s = e.init('a.b',null);
+
+			assert.strictEqual(s.code, 'OK');
+			// // null matches any empty object that is on a subpath of a.b
+			assert.ok( e.matches({}) );
+			assert.ok( e.matches({'a':{}}) );
+			assert.ok( e.matches({'a':[{}]}) );
+
+			assert.ok( e.matches({'a':{'b':null}} ) );
+			// b does not exist as an element in array under a.
+			assert.ok( !e.matches({'a':[]}) );
+			assert.ok( !e.matches({'a':[null]}) );
+			assert.ok( !e.matches({'a':[1,2]}) );
+			// a.b exists but is not null.
+			assert.ok( !e.matches({'a':{'b':4}} ) );
+			assert.ok( !e.matches({'a':{'b':{}}} ) );
+			// A non-existent field is treated same way as an empty bson object
+			assert.ok( e.matches({'b':4} ) );
+
+			assert.ok( e.matches({"b":"stuff"}) );
+		},
+		"should match MinKey" : function(){
+			var e = new EqualityMatchExpression();
+			var s = e.init('a',MinKey);
+			assert.ok( e.matches({'a': MinKey}) );
+			assert.ok( !e.matches({'a':MaxKey}) );
+			assert.ok( !e.matches({'a':4}) );
+		},
+		"should match MaxKey" : function(){
+			var e = new EqualityMatchExpression();
+			var s = e.init('a',MaxKey);
+			assert.ok( e.matches({'a':MaxKey}) );
+			assert.ok( !e.matches({'a': MinKey}) );
+			assert.ok( !e.matches({'a':4}) );
 		},
+
 		"should match full array" : function() {
 			var e = new EqualityMatchExpression();
 			var s = e.init('a',[1,2]);
@@ -128,7 +154,7 @@ module.exports = {
 			var a = new EqualityMatchExpression();
 			var b = new EqualityMatchExpression();
 			var c = new EqualityMatchExpression();
-			
+
 
 			assert.strictEqual( a.init('a',5).code, 'OK' );
 			assert.strictEqual( b.init('a',5).code, 'OK' );
@@ -139,10 +165,8 @@ module.exports = {
 			assert.ok( ! a.equivalent(c) );
 		}
 
-
 	}
 
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 7 - 7
test/lib/pipeline/matcher/ExistsMatchExpression.js

@@ -9,16 +9,16 @@ module.exports = {
 		"should match an element": function (){
 			var e = new ExistsMatchExpression();
 			var s = e.init('a');
-			
+
 			assert.strictEqual(s.code, 'OK');
 			assert.ok( e.matches({'a':5}) );
 			assert.ok( e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':{}}) );	
+			assert.ok( ! e.matches({'a':{}}) );
 		},
 		"should match a boolean":function() {
 			var e = new ExistsMatchExpression();
 			var s = e.init('a');
-			
+
 			assert.strictEqual( s.code, 'OK' );
 			assert.ok( e.matches({'a':5}) );
 			assert.ok( ! e.matches({}) );
@@ -27,7 +27,7 @@ module.exports = {
 		"should match a number":function() {
 			var e = new ExistsMatchExpression();
 			var s = e.init('a');
-			
+
 			assert.strictEqual( s.code, 'OK' );
 			assert.ok( e.matches({'a':1}) );
 			assert.ok( e.matches({'a':null}) );
@@ -36,9 +36,9 @@ module.exports = {
 		"should match an array":function() {
 			var e = new ExistsMatchExpression();
 			var s = e.init('a');
-			
+
 			assert.strictEqual( s.code, 'OK' );
-			assert.ok( e.matches({'a':[4,5.5]}) );	
+			assert.ok( e.matches({'a':[4,5.5]}) );
 		},
 		"should yield an elemMatchKey":function() {
 			var e = new ExistsMatchExpression();
@@ -49,7 +49,7 @@ module.exports = {
 
 			assert.ok( ! e.matches({'a':1}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
-			
+
 			assert.ok( e.matches({'a':{'b':6}}));
 			assert.ok( ! m.hasElemMatchKey() );
 

+ 51 - 0
test/lib/pipeline/matcher/FalseMatchExpression.js

@@ -0,0 +1,51 @@
+"use strict";
+var assert = require("assert"),
+	FalseMatchExpression = require("../../../../lib/pipeline/matcher/FalseMatchExpression");
+
+
+module.exports = {
+	"FalseMatchExpression": {
+
+		"Constructor": function (){
+			var e = new FalseMatchExpression();
+			assert.equal(e._matchType, "ALWAYS_FALSE");
+		},
+
+		"DebugString": function () {
+			var e = new FalseMatchExpression();
+			assert.equal(e.debugString(0), "$false\n");
+		},
+
+		"Equivalent": function () {
+			var a = new FalseMatchExpression(),
+				b = new FalseMatchExpression();
+			assert.equal(a.equivalent(b), true);
+		},
+
+		"Matches": function () {
+			var e = new FalseMatchExpression();
+			assert.equal(e.matches({},{}), false);
+		},
+
+		"MatchesSingleElement": function () {
+			var e = new FalseMatchExpression();
+			assert.equal(e.matchesSingleElement({}), false);
+		},
+
+		"ShallowClone": function () {
+			var e = new FalseMatchExpression();
+			assert.deepEqual(e.shallowClone(), new FalseMatchExpression());
+		},
+
+		"toJson": function () {
+			var e = new FalseMatchExpression(),
+				obj = {};
+			assert.deepEqual(e.toJson(obj), {"$false":1});
+		}
+
+	}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+

+ 67 - 32
test/lib/pipeline/matcher/GTEMatchExpression.js

@@ -1,6 +1,7 @@
 "use strict";
 var assert = require("assert"),
-	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
+	BSON = require("bson"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails"),
 	GTEMatchExpression = require("../../../../lib/pipeline/matcher/GTEMatchExpression");
 
 
@@ -8,68 +9,102 @@ module.exports = {
 	"GTEMatchExpression": {
 		"should match scalars and strings properly": function (){
 			var e = new GTEMatchExpression();
-			var s = e.init('x',5);
+			var s = e.init("",5);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'x':5}) );
-			assert.ok( ! e.matches({'x':4}) );
-			assert.ok( e.matches({'x':6}) );
-			assert.ok( ! e.matches({'x': 'eliot'}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesSingleElement(5.5) );
+			assert.ok( e.matchesSingleElement(5) );
+			assert.ok( ! e.matchesSingleElement(4) );
+			assert.ok( ! e.matchesSingleElement( "foo" ) );
 		},
 		"should handle invalid End of Object Operand": function testInvalidEooOperand(){
 			var e = new GTEMatchExpression();
-			var s = e.init('',{});
+			var s = e.init("",{});
 
-			assert.strictEqual(s.code, 'BAD_VALUE');
+			assert.strictEqual(s.code, "BAD_VALUE");
 		},
 		"should match a pathed number":function() {
 			var e = new GTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':5.5}) );
-			assert.ok( ! e.matches({'a':4}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":5.5}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
 		},
 		"should match stuff in an array": function() {
 			var e = new GTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':[4,5.5]}) );
-			assert.ok( ! e.matches({'a':[1,2]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":[4,5.5]}) );
+			assert.ok( ! e.matchesJSON({"a":[1,2]}) );
 		},
 		"should not match full array" : function() {
 			var e = new GTEMatchExpression();
-			var s = e.init('a',[5]);
+			var s = e.init("a",[5]);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[6]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( ! e.matchesJSON({"a":[4]}) );
+			assert.ok( e.matchesJSON({"a":[5]}) );
+			assert.ok( e.matchesJSON({"a":[6]}) );
 		},
 		"should not match null" : function() {
 			var e = new GTEMatchExpression();
-			var s = e.init('a',null);
-		
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({}) );
-			assert.ok( e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':4}) );
+			var s = e.init("a",null);
+
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({}) );
+			assert.ok( e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			assert.ok( e.matchesJSON({"b":4}) );
+		},
+		"should match dot notation nulls": function() {
+			var e = new GTEMatchExpression();
+			var s = e.init("a.b",null);
+
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({}));
+			assert.ok(e.matchesJSON({a:null}));
+			assert.ok(e.matchesJSON({a:{}}));
+			assert.ok(e.matchesJSON({a:[{b: null}]}));
+			assert.ok(e.matchesJSON({a:[{a:4}, {b:4}]}));
+			assert.ok(!e.matchesJSON({a:[4]}));
+			assert.ok(!e.matchesJSON({a:[{b:4}]}));
+		},
+		"should match MinKey": function (){
+			var operand = {a:new BSON.MinKey()},
+				e = new GTEMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(e.matchesJSON({"a":4}), null);
+		},
+		"should match MaxKey": function (){
+			var operand = {a:new BSON.MaxKey()},
+				e = new GTEMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(!e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(!e.matchesJSON({"a":4}), null);
 		},
 		"should handle elemMatchKey":function() {
 			var e = new GTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 			var m = new MatchDetails();
 			m.requestElemMatchKey();
-			assert.strictEqual( s.code, 'OK' );
+			assert.strictEqual( s.code, "OK" );
 
-			assert.ok( ! e.matches({'a':4}, m) );
+			assert.ok( ! e.matchesJSON({"a":4}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':6}, m) );
+			assert.ok( e.matchesJSON({"a":6}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':[2,6,5]}, m));
+			assert.ok( e.matchesJSON({"a":[2,6,5]}, m));
 			assert.ok( m.hasElemMatchKey());
-			assert.strictEqual('1', m.elemMatchKey());
+			assert.strictEqual("1", m.elemMatchKey());
 		}
 	}
 };

+ 70 - 38
test/lib/pipeline/matcher/GTMatchExpression.js

@@ -1,75 +1,107 @@
 "use strict";
 var assert = require("assert"),
-	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
+	BSON = require("bson"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails"),
 	GTMatchExpression = require("../../../../lib/pipeline/matcher/GTMatchExpression");
 
 
 module.exports = {
 	"GTMatchExpression": {
-		"should match scalars and strings properly": function (){
+		"should handle invalid End of Object Operand": function (){
 			var e = new GTMatchExpression();
-			var s = e.init('x',5);
+			var s = e.init("",{});
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'x':5}) );
-			assert.ok( ! e.matches({'x':4}) );
-			assert.ok( e.matches({'x':6}) );
-			assert.ok( ! e.matches({'x': 'eliot'}) );
+			assert.strictEqual(s.code, "BAD_VALUE");
 		},
-		"should handle invalid End of Object Operand": function testInvalidEooOperand(){
+		"should match scalars":function() {
 			var e = new GTMatchExpression();
-			var s = e.init('',{});
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'BAD_VALUE');
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":5.5}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
 		},
-		"should match a pathed number":function() {
+		"should match array value": function() {
 			var e = new GTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':5.5}) );
-			assert.ok( ! e.matches({'a':4}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":[3,5.5]}) );
+			assert.ok( ! e.matchesJSON({"a":[2,4]}) );
 		},
-		"should match stuff in an array": function() {
+		"should match whole array": function() {
 			var e = new GTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",[5]);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':[3,5.5]}) );
-			assert.ok( ! e.matches({'a':[2,4]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( ! e.matchesJSON({"a":[4]}) );
+			assert.ok( ! e.matchesJSON({"a":[5]}) );
+			assert.ok( e.matchesJSON({"a":[6]}) );
+			// Nested array.
+			// XXX: The following assertion documents current behavior.
+			assert.ok( e.matchesJSON({"a":[[4]]}) );
+			assert.ok( e.matchesJSON({"a":[[5]]}) );
+			assert.ok( e.matchesJSON({"a":[[6]]}) );
 		},
-		"should not match full array" : function() {
+		"should match null" : function() {
 			var e = new GTMatchExpression();
-			var s = e.init('a',[5]);
+			var s = e.init("a",null);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[6]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( !e.matchesJSON({}) );
+			assert.ok( !e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			// A non-existent field is treated same way as an empty bson object
+			assert.ok( ! e.matchesJSON({"b":4}) );
 		},
-		"should not match null" : function() {
+		"should match dot notation null" : function() {
 			var e = new GTMatchExpression();
-			var s = e.init('a',null);
-		
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( !e.matches({}) );
-			assert.ok( !e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':4}) );
+			var s = e.init("a.b",null);
+
+			assert.strictEqual(s.code, "OK");
+			assert.ok( !e.matchesJSON({}) );
+			assert.ok( !e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			assert.ok( ! e.matchesJSON({"a":{}}) );
+			assert.ok( ! e.matchesJSON({"a":[{b:null}]}) );
+			assert.ok( ! e.matchesJSON({"a":[{a:4},{b:4}]}) );
+			assert.ok( ! e.matchesJSON({"a":[4]}) );
+			assert.ok( ! e.matchesJSON({"a":[{b:4}]}) );
+		},
+		"should match MinKey": function (){
+			var operand = {a:new BSON.MinKey()},
+				e = new GTMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok( ! e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(e.matchesJSON({"a":4}), null);
+		},
+		"should match MaxKey": function (){
+			var operand = {a:new BSON.MaxKey()},
+				e = new GTMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(!e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(!e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(!e.matchesJSON({"a":4}), null);
 		},
 		"should handle elemMatchKey":function() {
 			var e = new GTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 			var m = new MatchDetails();
 			m.requestElemMatchKey();
-			assert.strictEqual( s.code, 'OK' );
+			assert.strictEqual( s.code, "OK" );
 
-			assert.ok( ! e.matches({'a':4}, m) );
+			assert.ok( ! e.matchesJSON({"a":4}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':6}, m) );
+			assert.ok( e.matchesJSON({"a":6}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':[2,6,5]}, m));
+			assert.ok( e.matchesJSON({"a":[2,6,5]}, m));
 			assert.ok( m.hasElemMatchKey());
-			assert.strictEqual('1', m.elemMatchKey());
+			assert.strictEqual("1", m.elemMatchKey());
 		}
 	}
 };

+ 46 - 41
test/lib/pipeline/matcher/InMatchExpression.js

@@ -1,7 +1,10 @@
 "use strict";
 var assert = require("assert"),
 	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
-	InMatchExpression = require("../../../../lib/pipeline/matcher/InMatchExpression");
+	InMatchExpression = require("../../../../lib/pipeline/matcher/InMatchExpression"),
+	// TODO: replace the following with a real BSONTypes at some point
+	MinKey = new (function MinKey(){/*matcher does weird stuff with empty objects*/this.foo = 'bar';})(), // jshint ignore:line
+	MaxKey = new (function MaxKey(){/*matcher does weird stuff with empty objects*/this.foo = 'bar';})(); // jshint ignore:line
 
 
 module.exports = {
@@ -10,18 +13,16 @@ module.exports = {
 			var e = new InMatchExpression();
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
-			
-			e._arrayEntries._equalities = [1];
-			
-			assert.ok( e.matchesSingleElement(1) );	
-			assert.ok( ! e.matchesSingleElement(2) );			
+
+			e.getArrayFilterEntries().addEquality(1);
+
+			assert.ok( e.matchesSingleElement(1) );
+			assert.ok( ! e.matchesSingleElement(2) );
 		},
 		"should not match with an empty array": function() {
 			var e = new InMatchExpression();
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
-			
-			e._arrayEntries._equalities = [];
 
 			assert.ok( ! e.matchesSingleElement(2) );
 			assert.ok( ! e.matches({'a':null}) );
@@ -31,8 +32,11 @@ module.exports = {
 			var e = new InMatchExpression();
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
-		
-			e._arrayEntries._equalities = [1,'r',true,1];
+
+			e.getArrayFilterEntries().addEquality(1);
+			e.getArrayFilterEntries().addEquality('r');
+			e.getArrayFilterEntries().addEquality(true);
+			e.getArrayFilterEntries().addEquality(1);
 
 			assert.ok( e.matchesSingleElement( 1 ) );
 			assert.ok( e.matchesSingleElement( 'r' ) );
@@ -43,9 +47,9 @@ module.exports = {
 			var e = new InMatchExpression();
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
-		
-			e._arrayEntries._equalities = [5];
-			
+
+			e.getArrayFilterEntries().addEquality(5);
+
 			assert.ok( e.matches({'a':5}) );
 			assert.ok( ! e.matches({'a':4}) );
 		},
@@ -54,8 +58,8 @@ module.exports = {
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
 
-			e._arrayEntries._equalities = [5];
-			
+			e.getArrayFilterEntries().addEquality(5);
+
 			assert.ok( e.matches({'a':[5,6]}) );
 			assert.ok( ! e.matches({'a':[6,7]}) );
 			assert.ok( ! e.matches({'a':[[5]]}) );
@@ -65,50 +69,51 @@ module.exports = {
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
 
-			e._arrayEntries._equalities = [null];
-			
+			e.getArrayFilterEntries().addEquality(null);
+
 			assert.ok( e.matches({}) );
 			assert.ok( e.matches({'a':null}) );
 			assert.ok( ! e.matches({'a':4}) );
+			// A non-existent field is treated same way as an empty bson object
+			assert.ok( e.matches({'b':4}) );
 		},
-		/*"should match MinKey": function() {
+		"should match undefined": function() {
 			var e = new InMatchExpression();
 			var s = e.init('a');
-			var fakeCon = {'name':'MinKey'}, minkey = {}, maxkey = {};
-			minkey.contructor = fakeCon;
-			minkey.constructor.name='MinKey';
-			maxkey.constructor = fakeCon;
-			maxkey.constructor.name = 'MaxKey';
 			assert.strictEqual( s.code,'OK' );
 
-			e._arrayEntries._equalities = [minkey];
+			assert( e.getArrayFilterEntries().addEquality(undefined) !== 'OK' );
+		},
+		"should match MinKey": function() {
+			var e = new InMatchExpression();
+			var s = e.init('a');
+			assert.strictEqual( s.code,'OK' );
+
+			e._arrayEntries._equalities = [MinKey];
 
-			assert.ok( e.matches({'a':minkey}) );
-			assert.ok( ! e.matches({'a':maxkey}) );
+			assert.ok( e.matches({'a':MinKey}) );
+			assert.ok( ! e.matches({'a':MaxKey}) );
 			assert.ok( ! e.matches({'a':4}) );
 		},
-		"should match MaxKey": function() {	
+		"should match MaxKey": function() {
 			var e = new InMatchExpression();
 			var s = e.init('a');
-			var minkey = {}, maxkey = {};
-			minkey.contructor = {};
-			minkey.constructor.name='MinKey';
-			maxkey.constructor = {};
-			maxkey.constructor.name = 'MaxKey';
 			assert.strictEqual( s.code,'OK' );
 
-			e._arrayEntries._equalities = [minkey];
+			e._arrayEntries._equalities = [MaxKey];
 
-			assert.ok( ! e.matches({'a':minkey}) );
-			assert.ok( e.matches({'a':maxkey}) );
+			assert.ok( ! e.matches({'a':MinKey}) );
+			assert.ok( e.matches({'a':MaxKey}) );
 			assert.ok( ! e.matches({'a':4}) );
-		},*/
+		},
 		"should match a full array":function() {
 			var e = new InMatchExpression();
 			var s = e.init('a');
 			assert.strictEqual( s.code,'OK' );
 
-			e._arrayEntries._equalities = [[1,2],4,5];
+			e.getArrayFilterEntries().addEquality([1,2]);
+			e.getArrayFilterEntries().addEquality(4);
+			e.getArrayFilterEntries().addEquality(5);
 
 			assert.ok( e.matches({'a':[1,2]}) );
 			assert.ok( ! e.matches({'a':[1,2,3]}) );
@@ -121,8 +126,9 @@ module.exports = {
 			var m = new MatchDetails();
 
 			assert.strictEqual( s.code,'OK' );
-			
-			e._arrayEntries._equalities = [5,2];
+
+			e.getArrayFilterEntries().addEquality(5);
+			e.getArrayFilterEntries().addEquality(2);
 			m.requestElemMatchKey();
 			assert.ok( !e.matches({'a':4}, m) );
 			assert.ok( !m.hasElemMatchKey() );
@@ -131,11 +137,10 @@ module.exports = {
 			assert.ok( e.matches({'a':[1,2,5]}, m ));
 			assert.ok( m.hasElemMatchKey() );
 			assert.strictEqual( m.elemMatchKey(), '1' );
-		
+
 		}
 
 	}
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 85 - 42
test/lib/pipeline/matcher/LTEMatchExpression.js

@@ -1,75 +1,118 @@
 "use strict";
 var assert = require("assert"),
-	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
+	BSON = require("bson"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails"),
 	LTEMatchExpression = require("../../../../lib/pipeline/matcher/LTEMatchExpression");
 
 
 module.exports = {
 	"LTEMatchExpression": {
-		"should match scalars and strings properly": function (){
-			var e = new LTEMatchExpression();
-			var s = e.init('x',5);
-			
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'x':5}) );
-			assert.ok( e.matches({'x':4}) );
-			assert.ok( ! e.matches({'x':6}) );
-			assert.ok( ! e.matches({'x': 'eliot'}) );
+		"should match element": function (){
+			var operand = {$lte:5},
+				match = {a:4.5},
+				equalMatch = {a:5},
+				notMatch = {a:6},
+				notMatchWrongType = {a:"foo"},
+				lte = new LTEMatchExpression();
+			var s = lte.init("",operand.$lte);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(lte.matchesSingleElement(match.a));
+			assert.ok(lte.matchesSingleElement(equalMatch.a));
+			assert.ok(!lte.matchesSingleElement(notMatch.a));
+			assert.ok(!lte.matchesSingleElement(notMatchWrongType.a));
+		},
+		"should not work for invalid eoo operand": function(){
+			var operand = {},
+				lte = new LTEMatchExpression();
+			assert.ok(lte.init("", operand).code !== "OK");
+		},
+		"should match scalars properly": function (){
+			var operand = {$lte:5},
+				lte = new LTEMatchExpression();
+			var s = lte.init("a",operand.$lte);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(lte.matchesJSON({"a":4.5}, null));
+			assert.ok(!lte.matchesJSON({"a":6}), null);
 		},
-		"should handle invalid End of Object Operand": function testInvalidEooOperand(){
+		"should match array value": function() {
 			var e = new LTEMatchExpression();
-			var s = e.init('',{});
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'BAD_VALUE');
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":[6,4.5]}) );
+			assert.ok( ! e.matchesJSON({"a":[6,7]}) );
 		},
-		"should match a pathed number":function() {
-			var e = new LTEMatchExpression();
-			var s = e.init('a',5);
+		"should match whole array" : function() {
+			var e = new LTEMatchExpression(),
+				s = e.init("a",[5]);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':4.5}) );
-			assert.ok( ! e.matches({'a':6}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":[4]}));
+			assert.ok(e.matchesJSON({"a":[5]}));
+			assert.ok(!e.matchesJSON({"a":[6]}));
+			assert.ok(e.matchesJSON({"a":[[4]]}));
+			assert.ok(e.matchesJSON({"a":[[5]]}));
+			assert.ok(!e.matchesJSON({"a":[[6]]}));
 		},
-		"should match stuff in an array": function() {
+		"should match null" : function() {
 			var e = new LTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",null);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':[6,4.5]}) );
-			assert.ok( ! e.matches({'a':[6,7]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({}) );
+			assert.ok( e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			// A non-existent field is treated same way as an empty bson object
+			assert.ok( e.matchesJSON({"b":4}) );
 		},
-		"should not match full array" : function() {
+		"should match dot notation null" : function() {
 			var e = new LTEMatchExpression();
-			var s = e.init('a',[5]);
+			var s = e.init("a.b",null);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[4]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({}) );
+			assert.ok( e.matchesJSON({"a":null}) );
+			assert.ok( e.matchesJSON({"a":4}) );
+			assert.ok( e.matchesJSON({"a":{}}) );
+			assert.ok( e.matchesJSON({"a":[{b:null}]}) );
+			assert.ok( e.matchesJSON({"a":[{a:4},{b:4}]}) );
+			assert.ok( ! e.matchesJSON({"a":[4]}) );
+			assert.ok( ! e.matchesJSON({"a":[{b:4}]}) );
 		},
-		"should not match null" : function() {
-			var e = new LTEMatchExpression();
-			var s = e.init('a',null);
-		
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({}) );
-			assert.ok( e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':4}) );
+		"should match MinKey": function (){
+			var operand = {a:new BSON.MinKey()},
+				e = new LTEMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(!e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(!e.matchesJSON({"a":4}), null);
+		},
+		"should match MaxKey": function (){
+			var operand = {a:new BSON.MaxKey()},
+				e = new LTEMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(e.matchesJSON({"a":4}), null);
 		},
 		"should handle elemMatchKey":function() {
 			var e = new LTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 			var m = new MatchDetails();
 			m.requestElemMatchKey();
-			assert.strictEqual( s.code, 'OK' );
+			assert.strictEqual( s.code, "OK" );
 
-			assert.ok( ! e.matches({'a':6}, m) );
+			assert.ok( ! e.matchesJSON({"a":6}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':4}, m) );
+			assert.ok( e.matchesJSON({"a":4}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':[6,2,5]}, m));
+			assert.ok( e.matchesJSON({"a":[6,2,5]}, m));
 			assert.ok( m.hasElemMatchKey());
-			assert.strictEqual('1', m.elemMatchKey());
+			assert.strictEqual("1", m.elemMatchKey());
 		}
 
 	}

+ 93 - 52
test/lib/pipeline/matcher/LTMatchExpression.js

@@ -1,87 +1,128 @@
 "use strict";
 var assert = require("assert"),
-	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
+	BSON = require("bson"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails"),
 	LTMatchExpression = require("../../../../lib/pipeline/matcher/LTMatchExpression");
 
 
 module.exports = {
 	"LTMatchExpression": {
-		"should match scalars and strings properly": function (){
-			var e = new LTMatchExpression();
-			var s = e.init('x',5);
-			
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'x':5}) );
-			assert.ok( e.matches({'x':4}) );
-			assert.ok( ! e.matches({'x':6}) );
-			assert.ok( ! e.matches({'x': 'eliot'}) );
+		"should match element": function (){
+			var operand = {$lt:5},
+				match = {a:4.5},
+				notMatch = {a:6},
+				notMatchEqual = {a:5},
+				notMatchWrongType = {a:"foo"},
+				lt = new LTMatchExpression();
+			var s = lt.init("",operand.$lt);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(lt.matchesSingleElement(match.a));
+			assert.ok(!lt.matchesSingleElement(notMatch.a));
+			assert.ok(!lt.matchesSingleElement(notMatchEqual.a));
+			assert.ok(!lt.matchesSingleElement(notMatchWrongType.a));
 		},
-		"should handle invalid End of Object Operand": function testInvalidEooOperand(){
-			var e = new LTMatchExpression();
-			var s = e.init('',{});
-
-			assert.strictEqual(s.code, 'BAD_VALUE');
+		"should not work for invalid eoo operand": function(){
+			var operand = {},
+				lt = new LTMatchExpression();
+			assert.ok(lt.init("", operand).code !== "OK");
 		},
-		"should match a pathed number":function() {
+		"should match scalars properly": function (){
+			var operand = {$lt:5},
+				lt = new LTMatchExpression();
+			var s = lt.init("a",operand.$lt);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(lt.matchesJSON({"a":4.5}, null));
+			assert.ok(!lt.matchesJSON({"a":6}), null);
+		},
+		"should match scalars with empty keys properly": function (){
+			var operand = {$lt:5},
+				lt = new LTMatchExpression();
+			var s = lt.init("",operand.$lt);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(lt.matchesJSON({"":4.5}, null));
+			assert.ok(!lt.matchesJSON({"":6}), null);
+		},
+		"should match array value": function() {
 			var e = new LTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':4.5}) );
-			assert.ok( ! e.matches({'a':6}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( e.matchesJSON({"a":[6,4.5]}) );
+			assert.ok( ! e.matchesJSON({"a":[6,7]}) );
 		},
-		"should match an empty pathed number":function() {
-			var e = new LTMatchExpression();
-			var s = e.init('',5);
+		"should match whole array" : function() {
+			var e = new LTMatchExpression(),
+				s = e.init("a",[5]);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'':4.5}) );
-			assert.ok( ! e.matches({'':6}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok(e.matchesJSON({"a":[4]}));
+			assert.ok(!e.matchesJSON({"a":[5]}));
+			assert.ok(!e.matchesJSON({"a":[6]}));
+			// Nested array.
+			assert.ok(e.matchesJSON({"a":[[4]]}));
+			assert.ok(!e.matchesJSON({"a":[[5]]}));
+			assert.ok(!e.matchesJSON({"a":[[6]]}));
 		},
-		"should match stuff in an array": function() {
+		"should match null" : function() {
 			var e = new LTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",null);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':[6,4.5]}) );
-			assert.ok( ! e.matches({'a':[6,7]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( ! e.matchesJSON({}) );
+			assert.ok( ! e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			// A non-existent field is treated same way as an empty bson object
+			assert.ok( ! e.matchesJSON({"b":4}) );
 		},
-		"should not match full array" : function() {
+		"should match dot notation null" : function() {
 			var e = new LTMatchExpression();
-			var s = e.init('a',[5]);
+			var s = e.init("a.b",null);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[4]}) );
+			assert.strictEqual(s.code, "OK");
+			assert.ok( ! e.matchesJSON({}) );
+			assert.ok( ! e.matchesJSON({"a":null}) );
+			assert.ok( ! e.matchesJSON({"a":4}) );
+			assert.ok( ! e.matchesJSON({"a":{}}) );
+			assert.ok( ! e.matchesJSON({"a":[{b:null}]}) );
+			assert.ok( ! e.matchesJSON({"a":[{a:4},{b:4}]}) );
+			assert.ok( ! e.matchesJSON({"a":[4]}) );
+			assert.ok( ! e.matchesJSON({"a":[{b:4}]}) );
 		},
-		"should not match null" : function() {
-			var e = new LTMatchExpression();
-			var s = e.init('a',null);
-		
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({}) );
-			assert.ok( ! e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':4}) );
+		"should match MinKey": function (){
+			var operand = {a:new BSON.MinKey()},
+				e = new LTMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(!e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(!e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(!e.matchesJSON({"a":4}), null);
+		},
+		"should match MaxKey": function (){
+			var operand = {a:new BSON.MaxKey()},
+				e = new LTMatchExpression();
+			var s = e.init("a",operand.a);
+			assert.strictEqual(s.code, "OK");
+			assert.ok(!e.matchesJSON({"a":new BSON.MaxKey()}, null));
+			assert.ok(e.matchesJSON({"a":new BSON.MinKey()}, null));
+			assert.ok(e.matchesJSON({"a":4}), null);
 		},
 		"should handle elemMatchKey":function() {
 			var e = new LTMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init("a",5);
 			var m = new MatchDetails();
 			m.requestElemMatchKey();
-			assert.strictEqual( s.code, 'OK' );
+			assert.strictEqual( s.code, "OK" );
 
-			assert.ok( ! e.matches({'a':6}, m) );
+			assert.ok( ! e.matchesJSON({"a":6}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':4}, m) );
+			assert.ok( e.matchesJSON({"a":4}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matches({'a':[6,2,5]}, m));
+			assert.ok( e.matchesJSON({"a":[6,2,5]}, m));
 			assert.ok( m.hasElemMatchKey());
-			assert.strictEqual('1', m.elemMatchKey());
+			assert.strictEqual("1", m.elemMatchKey());
 		}
-
-
-
 	}
 };
 

+ 72 - 0
test/lib/pipeline/matcher/ListOfMatchExpression.js

@@ -0,0 +1,72 @@
+"use strict";
+var assert = require("assert"),
+	MatchExpression = require("../../../../lib/pipeline/matcher/MatchExpression"),
+	ListOfMatchExpression = require("../../../../lib/pipeline/matcher/ListOfMatchExpression");
+
+
+module.exports = {
+	"ListOfMatchExpression": {
+
+		"Constructor": function (){
+			var e = new ListOfMatchExpression('AND');
+			assert.equal(e._matchType, "AND");
+		},
+
+		"Add": function () {
+			var e = new ListOfMatchExpression();
+			e.add(new MatchExpression("OR"));
+			assert.equal(e._expressions[0]._matchType, "OR");
+		},
+
+		"Add2": function () {
+			var e = new ListOfMatchExpression();
+			e.add(new MatchExpression("OR"));
+			e.add(new MatchExpression("NOT"));
+			assert.equal(e._expressions[0]._matchType, "OR");
+			assert.equal(e._expressions[1]._matchType, "NOT");
+		},
+
+		"ClearAndRelease": function () {
+			var e = new ListOfMatchExpression();
+			e.add(new MatchExpression("OR"));
+			e.add(new MatchExpression("NOT"));
+			e.clearAndRelease();
+			assert.equal(e._expressions.length, 0);
+		},
+
+		"NumChildren": function () {
+			var e = new ListOfMatchExpression();
+			e.add(new MatchExpression("OR"));
+			e.add(new MatchExpression("NOT"));
+			assert.equal(e.numChildren(), 2);
+		},
+
+		"GetChild": function () {
+			var e = new ListOfMatchExpression(),
+				match1 = new MatchExpression("NOT");
+			e.add(new MatchExpression("OR"));
+			e.add(match1);
+			assert.deepEqual(e.getChild(1), match1);
+		},
+
+		"GetChildVector": function () {
+			var e = new ListOfMatchExpression(),
+				match0 = new MatchExpression("NOT"),
+				match1 = new MatchExpression("OR");
+			e.add(match0);
+			e.add(match1);
+			assert.equal(e.getChildVector().length, 2);
+		},
+
+		"Equivalent": function () {
+			var e = new ListOfMatchExpression('TEXT'),
+				f = new ListOfMatchExpression("TEXT");
+			assert.equal(e.equivalent(f), true);
+		}
+
+	}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+

+ 62 - 0
test/lib/pipeline/matcher/MatchDetails.js

@@ -0,0 +1,62 @@
+"use strict";
+var assert = require("assert"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails.js");
+
+module.exports = {
+	"MatchDetails": {
+		"Constructor": function() {
+			var md = new MatchDetails();
+			assert.equal(md._elemMatchKeyRequested, false);
+			assert.equal(md._loadedRecord, false);
+			assert.equal(md._elemMatchKey, undefined);
+			assert(md instanceof MatchDetails);
+		},
+
+		"ResetOutput": function() {
+			var md = new MatchDetails();
+			md.setLoadedRecord(1);
+			assert.equal(md._loadedRecord, 1);
+			md.resetOutput();
+			assert.equal(md._loadedRecord, 0);
+			assert.equal(md._elemMatchKey, undefined);
+		},
+
+		"toString": function() {
+			var md = new MatchDetails();
+			assert(typeof md.toString() === "string");
+		},
+
+		"setLoadedRecord": function() {
+			var md = new MatchDetails(),
+				rec = {"TEST":1};
+			md.setLoadedRecord(rec);
+			assert.deepEqual(md._loadedRecord, rec);
+		},
+
+		"hasLoadedRecord": function() {
+			var md = new MatchDetails(),
+				rec = true;
+			md.setLoadedRecord(rec);
+			assert.equal(md.hasLoadedRecord(), true);
+		},
+
+		"requestElemMatchKey": function() {
+			var md = new MatchDetails();
+			md.requestElemMatchKey();
+			assert(md.needRecord, true);	//should be true after request
+		},
+
+		"setElemMatchKey": function() {
+			var md = new MatchDetails(),
+				key = "TEST";
+			md.setElemMatchKey(key);
+			assert.equal(md.hasElemMatchKey(), false);	//should not be set unless requested
+			md.requestElemMatchKey();
+			md.setElemMatchKey(key);
+			assert.equal(md.hasElemMatchKey(), true);
+			assert.equal(md.elemMatchKey(), key);
+		}
+	}
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 91 - 34
test/lib/pipeline/matcher/MatchExpressionParser.js

@@ -30,7 +30,7 @@ module.exports = {
 				q3 = {'x':5, 'y':{'$isolated':1}};
 			var parser = new MatchExpressionParser();
 			var t = parser.parse(q1);
-			
+
 			assert.strictEqual(parser.parse(q1).code, 'OK');
 			assert.strictEqual(parser.parse(q2).code, 'OK');
 			assert.strictEqual(parser.parse(q3).code, 'BAD_VALUE');
@@ -38,7 +38,7 @@ module.exports = {
 		"Should parse and match $size with an int": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$size':2}};
-				
+
 			var res = parser.parse(q);
 			assert.strictEqual(res.code,'OK',res.description);
 			assert.ok( ! res.result.matches({'x':1}) );
@@ -49,7 +49,7 @@ module.exports = {
 		"Should parse and match $size with a string argument": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$size':'a'}};
-			
+
 			var res = parser.parse( q );
 			assert.strictEqual(res.code,'OK',res.description);
 			assert.ok( ! res.result.matches({'x':1}) );
@@ -78,7 +78,7 @@ module.exports = {
 		"Should parse $elemMatch : {x:1,y:2}": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$elemMatch': {'x':1,'y':2}}};
-			
+
 			var res = parser.parse( q );
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':1}) );
@@ -99,7 +99,7 @@ module.exports = {
 		"Should parse and match $all:[1,2]" : function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$all':[1,2]}};
-			
+
 			var res = parser.parse( q );
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':1}) );
@@ -119,9 +119,9 @@ module.exports = {
 		"Should not allow large regex patterns": function () {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$all':[new RegExp((new Array(50*1000+1)).join('z'))] }};
-			
+
 			var res = parser.parse( q );
-			assert.strictEqual( res.code, 'BAD_VALUE' );	
+			assert.strictEqual( res.code, 'BAD_VALUE' );
 		},
 		"Should parse and match some simple regex patterns": function() {
 			var parser = new MatchExpressionParser();
@@ -174,7 +174,7 @@ module.exports = {
 
 			var res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
-			
+
 			q = {'x':{'$all':[5,{'$elemMatch':{'x':1,'y':2}}]}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
@@ -206,7 +206,7 @@ module.exports = {
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( res.result.matches({'x':1}) );
 			assert.ok( ! res.result.matches({'x':2}) );
-			assert.ok( ! res.result.matches({'x':3}) );	
+			assert.ok( ! res.result.matches({'x':3}) );
 		},
 		"Should parse and match simple $gte": function() {
 			var parser = new MatchExpressionParser();
@@ -216,7 +216,7 @@ module.exports = {
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':1}) );
 			assert.ok( res.result.matches({'x':2}) );
-			assert.ok( res.result.matches({'x':3}) );	
+			assert.ok( res.result.matches({'x':3}) );
 		},
 		"Should parse and matc simple $lte": function() {
 			var parser = new MatchExpressionParser();
@@ -256,7 +256,7 @@ module.exports = {
 			q = {'x':{'$mod':['q',2]}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
-		
+
 			q = {'x':{'$mod':3}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
@@ -272,20 +272,9 @@ module.exports = {
 			var res = parser.parse( q );
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( res.result.matches({'x':5}) );
-			assert.ok( ! res.result.matches({'x':4}) );	
+			assert.ok( ! res.result.matches({'x':4}) );
 			assert.ok( res.result.matches({'x':8}) );
 		},
-		"Should treat a second arg to $mod that is a string as a 0": function() {
-			var parser = new MatchExpressionParser();
-			var q = {'x':{'$mod':[2,'r']}};
-
-			var res = parser.parse( q );
-			assert.strictEqual( res.code,'OK',res.description );
-			assert.ok( res.result.matches({'x':2}) );
-			assert.ok( res.result.matches({'x':4}) );
-			assert.ok( ! res.result.matches({'x':5}) );
-			assert.ok( ! res.result.matches({'x':'a'}) );
-		},
 		"Should parse and match a simple $in": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x': {'$in':[2,3]}};
@@ -317,7 +306,7 @@ module.exports = {
 
 			var res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
-	
+
 			q = {'x':{'$in': [{'$regex': str}]}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
@@ -392,11 +381,11 @@ module.exports = {
 
 			var res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
-		
+
 			q = {'x':{'$optionas': 'i'}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
-		
+
 			q = {'x':{'$options':'i'}};
 			res = parser.parse( q );
 			assert.strictEqual( res.code, 'BAD_VALUE' );
@@ -422,7 +411,7 @@ module.exports = {
 		"Should parse and match String $type": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$type': 2 }};
-			
+
 			var res = parser.parse( q );
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( res.result.matches({'x': 'abc'}) );
@@ -445,12 +434,12 @@ module.exports = {
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':{}}) );
 			assert.ok( ! res.result.matches({'x':5}) );
-			assert.ok( res.result.matches({'x':null}) );		
+			assert.ok( res.result.matches({'x':null}) );
 		},
 		"Should parse but not match a type beyond typemax in $type": function() {
 			var parser = new MatchExpressionParser();
 			var q = {'x':{'$type': 1000}};
-			
+
 			var res = parser.parse( q );
 			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':5}) );
@@ -479,7 +468,7 @@ module.exports = {
 			var q = {'$or':[{'$or':[{'x':1},{'y':2}]}]};
 
 			var res = parser.parse( q );
-			assert.strictEqual( res.code,'OK',res.description );	
+			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( res.result.matches({'x':1}) );
 			assert.ok( res.result.matches({'y':2}) );
 			assert.ok( ! res.result.matches({'x':3}) );
@@ -503,7 +492,7 @@ module.exports = {
 			var q = {'$nor':[{'x':1},{'y':2}]};
 
 			var res = parser.parse( q );
-			assert.strictEqual( res.code,'OK',res.description );	
+			assert.strictEqual( res.code,'OK',res.description );
 			assert.ok( ! res.result.matches({'x':1}) );
 			assert.ok( ! res.result.matches({'y':2}) );
 			assert.ok( res.result.matches({'x':3}) );
@@ -518,6 +507,77 @@ module.exports = {
 			assert.ok( res.result.matches({'x':2}) );
 			assert.ok( ! res.result.matches({'x':8}) );
 		},
+		"should allow trees less than the maximum recursion depth": function() {
+			var parser = new MatchExpressionParser(),
+				depth = 60,
+				q = "",
+				i;
+
+			for (i = 0; i < depth/2; i++) {
+				q = q + '{"$and": [{"a":3}, {"$or": [{"b":2},';
+			}
+			q = q + '{"b": 4}';
+			for (i = 0; i < depth/2; i++) {
+				q = q + "]}]}";
+			}
+
+			var res = parser.parse(JSON.parse(q));
+			assert.strictEqual(res.code, 'OK', res.description);
+		},
+		"should error when depth limit is exceeded": function() {
+			var parser = new MatchExpressionParser(),
+				depth = 105,
+				q = "",
+				i;
+
+			for (i = 0; i < depth/2; i++) {
+				q = q + '{"$and": [{"a":3}, {"$or": [{"b":2},';
+			}
+			q = q + '{"b": 4}';
+			for (i = 0; i < depth/2; i++) {
+				q = q + "]}]}";
+			}
+
+			var res = parser.parse(JSON.parse(q));
+			assert.strictEqual(res.description.substr(0, 43), 'exceeded maximum query tree depth of 100 at');
+			assert.strictEqual(res.code, 'BAD_VALUE');
+		},
+		"should error when depth limit is reached through a $not": function() {
+			var parser = new MatchExpressionParser(),
+				depth = 105,
+				q = '{"a": ',
+				i;
+
+			for (i = 0; i < depth; i++) {
+				q = q + '{"$not": ';
+			}
+			q = q + '{"$eq": 5}';
+			for (i = 0; i < depth+1; i++) {
+				q = q + "}";
+			}
+
+			var res = parser.parse(JSON.parse(q));
+			assert.strictEqual(res.description.substr(0, 43), 'exceeded maximum query tree depth of 100 at');
+			assert.strictEqual(res.code, 'BAD_VALUE');
+		},
+		"should error when depth limit is reached through an $elemMatch": function() {
+			var parser = new MatchExpressionParser(),
+				depth = 105,
+				q = '',
+				i;
+
+			for (i = 0; i < depth; i++) {
+				q = q + '{"a": {"$elemMatch": ';
+			}
+			q = q + '{"b": 5}';
+			for (i = 0; i < depth; i++) {
+				q = q + "}}";
+			}
+
+			var res = parser.parse(JSON.parse(q));
+			assert.strictEqual(res.description.substr(0, 43), 'exceeded maximum query tree depth of 100 at');
+			assert.strictEqual(res.code, 'BAD_VALUE');
+		},
 		"Should parse $not $regex and match properly": function() {
 			var parser = new MatchExpressionParser();
 			var a = /abc/i;
@@ -528,10 +588,7 @@ module.exports = {
 			assert.ok( ! res.result.matches({'x':'ABC'}) );
 			assert.ok( res.result.matches({'x':'AC'}) );
 		}
-
-
 	}
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

+ 78 - 0
test/lib/pipeline/matcher/MatchExpression_test.js

@@ -0,0 +1,78 @@
+"use strict";
+
+var assert = require("assert"),
+	EqualityMatchExpression = require("../../../../lib/pipeline/matcher/EqualityMatchExpression"),
+	LTEMatchExpression = require("../../../../lib/pipeline/matcher/LTEMatchExpression"),
+	LTMatchExpression = require("../../../../lib/pipeline/matcher/LTMatchExpression"),
+	GTEMatchExpression = require("../../../../lib/pipeline/matcher/GTEMatchExpression"),
+	GTMatchExpression = require("../../../../lib/pipeline/matcher/GTMatchExpression");
+
+module.exports = {
+
+	"LeafMatchExpression":{
+
+		"Equal1":function Equal1() {
+			var temp = {x:5},
+				e = new EqualityMatchExpression();
+			e.init("x", temp.x);
+			assert(e.matchesJSON({x:5}));
+			assert(e.matchesJSON({x:[5]}));
+			assert(e.matchesJSON({x:[1,5]}));
+			assert(e.matchesJSON({x:[1,5,2]}));
+			assert(e.matchesJSON({x:[5,2]}));
+
+			assert(!(e.matchesJSON({x:null})));
+			assert(!(e.matchesJSON({x:6})));
+			assert(!(e.matchesJSON({x:[4,2]})));
+			assert(!(e.matchesJSON({x:[[5]]})));
+		},
+
+		"Comp1":{
+
+			"LTEMatchExpression": function () {
+				var temp = {x:5},
+					e = new LTEMatchExpression();
+				e.init("x", temp.x);
+				assert(e.matchesJSON({x:5}));
+				assert(e.matchesJSON({x:4}));
+				assert(!(e.matchesJSON({x:6})));
+				assert(!(e.matchesJSON({x:"eliot"})));
+			},
+
+			"LTMatchExpression": function () {
+				var temp = {x:5},
+					e = new LTMatchExpression();
+				e.init("x", temp.x);
+				assert(!(e.matchesJSON({x:5})));
+				assert(e.matchesJSON({x:4}));
+				assert(!(e.matchesJSON({x:6})));
+				assert(!(e.matchesJSON({x:"eliot"})));
+			},
+
+			"GTEMatchExpression": function () {
+				var temp = {x:5},
+					e = new GTEMatchExpression();
+				e.init("x", temp.x);
+				assert(e.matchesJSON({x:5}));
+				assert(!(e.matchesJSON({x:4})));
+				assert(e.matchesJSON({x:6}));
+				assert(!(e.matchesJSON({x:"eliot"})));
+			},
+
+			"GTMatchExpression": function () {
+				var temp = {x:5},
+					e = new GTMatchExpression();
+				e.init("x", temp.x);
+				assert(!(e.matchesJSON({x:5})));
+				assert(!(e.matchesJSON({x:4})));
+				assert(e.matchesJSON({x:6}));
+				assert(!(e.matchesJSON({x:"eliot"})));
+			}
+
+		}
+
+	}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 86 - 0
test/lib/pipeline/matcher/Matcher2.js

@@ -0,0 +1,86 @@
+"use strict";
+
+var assert = require("assert"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails.js"),
+	Matcher2 = require("../../../../lib/pipeline/matcher/Matcher2.js");
+
+module.exports = {
+	"Matcher2": {
+		"Constructor": function() {
+			var m = new Matcher2({"a":1});
+		},
+
+		"Basic": function() {
+			var query = {"a":"b"},
+				m = new Matcher2(query);
+			assert(m.matches(query));
+		},
+
+		"DoubleEqual": function() {
+			var query = {"a":5},
+				m = new Matcher2(query);
+			assert(m.matches(query));
+		},
+
+		"MixedNumericEqual": function() {	//not sure if we need this.  Same as DoubleEqual in munge
+			var query = {"a":5},
+				m = new Matcher2(query);
+			assert(m.matches(query));
+		},
+
+		"MixedNumericGt": function() {
+			var query = {"a":{"$gt":4}},
+				m = new Matcher2(query);
+			assert.ok(m.matches({"a":5}));
+		},
+
+		"MixedNumericIN": function() {
+			var query = {"a":{"$in":[4,6]}},
+				m = new Matcher2(query);
+			assert.ok(m.matches({"a":4.0}));
+			assert.ok(!m.matches({"a":5.0}));
+			assert.ok(m.matches({"a":4}));
+		},
+
+		"MixedNumericEmbedded": function() {
+			var query = {"a":{"x":1}},
+				m = new Matcher2(query);
+			assert.ok(m.matches({"a":{"x":1}}));
+			assert.ok(m.matches({"a":{"x":1.0}}));
+		},
+
+		"Size": function() {
+			var query = {"a":{"$size":4}},
+				m = new Matcher2(query);
+			assert.ok(m.matches({"a":[1,2,3,4]}));
+			assert.ok(!m.matches({"a":[1,2,3]}));
+			assert.ok(!m.matches({"a":[1,2,3,'a','b']}));
+			assert.ok(!m.matches({"a":[[1,2,3,4]]}));
+		},
+
+		"WithinBox - mongo Geo function, not porting": function() {},
+
+		"WithinPolygon - mongo Geo function, not porting": function() {},
+
+		"WithinCenter - mongo Geo function, not porting": function() {},
+
+		"ElemMatchKey": function() {
+			var query = {"a.b":1},
+				m = new Matcher2(query),
+				md = new MatchDetails();
+			md.requestElemMatchKey();
+			assert.ok(!md.hasElemMatchKey());
+			assert.ok(m.matches({"a":[{"b":1}]}, md));
+			assert.ok(md.hasElemMatchKey());
+			assert.equal("0", md.elemMatchKey());
+		},
+
+		"WhereSimple1 - mongo MapReduce function, not available ": function() {
+		},
+
+		"AllTiming - mongo benchmarking function, not available": function() {
+		}
+		}
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 4 - 4
test/lib/pipeline/matcher/NorMatchExpression.js

@@ -84,13 +84,13 @@ module.exports = {
 			orOp.add(sub2);
 
 			details.requestElemMatchKey();
-			assert.ok( orOp.matchesBSON({"a":[10], 'b':[10]}, details));
+			assert.ok( orOp.matchesJSON({"a":[10], 'b':[10]}, details));
 			assert.ok(!details.hasElemMatchKey());
 
-			assert.ok( ! orOp.matchesBSON({"a":[1], "b":[1, 2]}, details));
+			assert.ok( ! orOp.matchesJSON({"a":[1], "b":[1, 2]}, details));
 			assert.ok(!details.hasElemMatchKey());
-		
-			
+
+
 		}
 
 

+ 4 - 4
test/lib/pipeline/matcher/OrMatchExpression.js

@@ -84,13 +84,13 @@ module.exports = {
 			orOp.add(sub2);
 
 			details.requestElemMatchKey();
-			assert.ok(!orOp.matchesBSON({"a":[10], 'b':[10]}, details));
+			assert.ok(!orOp.matchesJSON({"a":[10], 'b':[10]}, details));
 			assert.ok(!details.hasElemMatchKey());
 
-			assert.ok(orOp.matchesBSON({"a":[1], "b":[1, 2]}, details));
+			assert.ok(orOp.matchesJSON({"a":[1], "b":[1, 2]}, details));
 			assert.ok(!details.hasElemMatchKey());
-		
-			
+
+
 		}
 
 

+ 216 - 0
test/lib/pipeline/matcher/RegexMatchExpression.js

@@ -0,0 +1,216 @@
+"use strict";
+var assert = require("assert"),
+	XRegExp = require("xregexp").XRegExp,
+	RegexMatchExpression = require("../../../../lib/pipeline/matcher/RegexMatchExpression"),
+	MatchDetails = require("../../../../lib/pipeline/matcher/MatchDetails");
+
+
+module.exports = {
+	"RegexMatchExpression": {
+
+		"should match an exact element": function(){
+			var match = {"a":"b"},
+				notMatch = {"a":"c"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "b", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.a));
+			assert(!regex.matchesSingleElement(notMatch.a));
+		},
+		"should error if the pattern is too large": function(){
+			var tooLargePattern = "";
+			for (var i = 0; i<32765; i++){tooLargePattern += '3';}
+			var regex = new RegexMatchExpression();
+			assert(regex.init("a", tooLargePattern, "").code !== 'OK');
+		},
+		"should match an element with a simple prefix": function(){
+			var match = {"x":"abc"},
+				notMatch = {"x":"adz"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "^ab", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element case sensitive": function(){
+			var match = {"x":"abc"},
+				notMatch = {"x":"ABC"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "abc", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element case insensitive": function(){
+			var match = {"x":"abc"},
+				matchUppercase = {"x":"ABC"},
+				notMatch = {"x":"adz"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "abc", "i").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(regex.matchesSingleElement(matchUppercase.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element multiline off": function(){
+			var match = {"x":"az"},
+				notMatch = {"x":"\naz"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "^a", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element multiline on": function(){
+			var match = {"x":"az"},
+				matchMultiline = {"x":"\naz"},
+				notMatch = {"x":"\n\n"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "^a", "m").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(regex.matchesSingleElement(matchMultiline.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element with extended off": function(){
+			var match = {"x":"a b"},
+				notMatch = {"x":"ab"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "a b", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element with extended on": function(){
+			var match = {"x":"ab"},
+				notMatch = {"x":"a b"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "a b", "x").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element with dot matches all off": function(){
+			var match = {"x":"a b"},
+				notMatch = {"x":"a\nb"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "a.b", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element with dot matches all on": function(){
+			var match = {"x":"a b"},
+				matchDotAll = {"x":"a\nb"},
+				notMatch = {"x":"ab"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "a.b", "s").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(regex.matchesSingleElement(matchDotAll.x));
+			assert(!regex.matchesSingleElement(notMatch.x));
+		},
+		"should match an element with multiple flags": function(){
+			var match = {"x":"\na\nb"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "^a.b", "ms").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+		},
+		"should match an element with type regex": function(){
+			var match = {"x":new XRegExp('yz', 'i')},
+				notMatchPattern = {"x":new XRegExp('r', 'i')},
+				notMatchFlags = {"x":new XRegExp('yz', 's')};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "yz", "i").code, 'OK');
+			
+			assert(regex.matchesSingleElement(match.x));
+			assert(!regex.matchesSingleElement(notMatchPattern.x));
+			assert(!regex.matchesSingleElement(notMatchFlags.x));
+		},
+	
+//skipped as we don't support symbols yet	
+/*
+    TEST( RegexMatchExpression, MatchesElementSymbolType ) {
+        BSONObj match = BSONObjBuilder().appendSymbol( "x", "yz" ).obj();
+        BSONObj notMatch = BSONObjBuilder().appendSymbol( "x", "gg" ).obj();
+        RegexMatchExpression regex;
+        ASSERT( regex.init( "", "yz", "" ).isOK() );
+        ASSERT( regex.matchesSingleElement( match.firstElement() ) );
+        ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) );
+    }
+*/
+
+		"should not match an element with the wrong type": function(){
+			var notMatchInt = {"x":1},
+				notMatchBool = {"x":true};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "1", "").code, 'OK');
+			
+			assert(!regex.matchesSingleElement(notMatchInt.x));
+			assert(!regex.matchesSingleElement(notMatchBool.x));
+		},
+
+		"should match an element that is Utf8": function(){
+			var matches = {"x":"\u03A9"};
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("", "^.*", "").code, 'OK');
+			
+			assert(regex.matchesSingleElement(matches.x));
+		},
+		
+		"should match an element that is scalar": function(){
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("a", "b", "").code, 'OK');
+			
+			assert(regex.matches({"a":"b"}));
+			assert(!regex.matches({"a":"c"}));
+		},
+		"should match an array value": function(){
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("a", "b", "").code, 'OK');
+			
+			assert(regex.matches({"a":["c","b"]}));
+			assert(!regex.matches({"a":["d","c"]}));
+		},
+		"should match null": function(){
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("a", "b", "").code, 'OK');
+			
+			assert(!regex.matches({}));
+			assert(!regex.matches({"a":null}));
+		},
+		"should match element keys": function(){
+			var regex = new RegexMatchExpression();
+			assert.strictEqual(regex.init("a", "b", "").code, 'OK');
+			var details = new MatchDetails();
+			details.requestElemMatchKey();
+			
+			assert(!regex.matches({"a":"c"}, details));
+			assert(!details.hasElemMatchKey());
+			assert(regex.matches({"a":"b"}, details));
+			assert(!details.hasElemMatchKey());
+			assert(regex.matches({"a":["c", "b"]}, details));
+			assert(details.hasElemMatchKey());
+			assert.strictEqual(details.elemMatchKey(), "1");
+		},
+		"should determine equivalency": function(){
+			var r1 = new RegexMatchExpression(),
+				r2 = new RegexMatchExpression(),
+				r3 = new RegexMatchExpression(),
+				r4 = new RegexMatchExpression();
+			assert.strictEqual(r1.init("a", "b", "").code, 'OK');
+			assert.strictEqual(r2.init("a", "b", "x").code, 'OK');
+			assert.strictEqual(r3.init("a", "c", "").code, 'OK');
+			assert.strictEqual(r4.init("b", "b", "").code, 'OK');
+			
+			assert(r1.equivalent(r1));
+			assert(!r1.equivalent(r2));
+			assert(!r1.equivalent(r3));
+			assert(!r1.equivalent(r4));
+		},
+
+	}
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 59 - 0
test/lib/pipeline/matcher/TextMatchExpression_test.js

@@ -0,0 +1,59 @@
+"use strict";
+
+var assert = require('assert'),
+	TextMatchExpression = require('../../../../lib/pipeline/matcher/TextMatchExpression.js'),
+	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails.js');
+
+module.exports = {
+	'TextMatchExpression': {
+		'Should match an element, regardless of what is provided.': function() {
+			var text = new TextMatchExpression(),
+				text2 = new TextMatchExpression();
+
+			assert.strictEqual(text.init('query', 'language').code, 'OK');
+			assert.strictEqual(text2.init('query2', 'language2').code, 'OK');
+
+			assert.ok(text.matchesSingleElement(text2)); // It'll always work. Just the way it is in source.
+		},
+
+		'Should return the query provided in the init.': function() {
+			var text = new TextMatchExpression();
+
+			text.init('query', 'language');
+
+			assert.strictEqual(text.getQuery(), 'query');
+		},
+
+		'Should return the language provided in the init.': function() {
+			var text = new TextMatchExpression();
+
+			text.init('query', 'language');
+
+			assert.strictEqual(text.getLanguage(), 'language');
+		},
+
+		'Should return equivalency.': function() {
+			var text1 = new TextMatchExpression(),
+				text2 = new TextMatchExpression(),
+				text3 = new TextMatchExpression();
+
+			text1.init('query', 'language');
+			text2.init('query', 'language');
+			text3.init('query2', 'language2');
+
+			assert.ok(text1.equivalent(text1));
+			assert.ok(text1.equivalent(text2));
+			assert.ok(!text1.equivalent(text3));
+		},
+
+		'Should return a shallow copy of the original text match expression.': function() {
+			var text1 = new TextMatchExpression(),
+				status = text1.init('query', 'language'),
+				text2 = text1.shallowClone();
+
+			assert.ok(text1.equivalent(text2));
+		}
+	}
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 72 - 76
test/lib/pipeline/matcher/TypeMatchExpression.js

@@ -5,120 +5,116 @@ var assert = require("assert"),
 
 
 module.exports = {
-	"TypeMatchExpression": {
-		"should match string type": function (){
+	'TypeMatchExpression': {
+		'Should match string type.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('', 2 );
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches('abc') );
-			assert.ok( ! e.matches(2) );
+			assert.strictEqual(e.init('', 2).code, 'OK');
 
+			assert.ok(e.matches('abc'));
+			assert.ok(! e.matches(2));
 		},
-		"should match null type": function() {
+
+		'Should match null type.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('',10 );
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches(null) );
-			assert.ok( ! e.matches(10) );
+			assert.strictEqual(e.init('', 10).code, 'OK');
 
+			assert.ok(e.matches(null));
+			assert.ok(!e.matches(10));
 		},
-		"should match unknown type": function() {
+
+		'Should match unknown type.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('', 1024);
-	
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches(1024) );
-			assert.ok( ! e.matches('abc') );
 
+			assert.strictEqual(e.init('', 1024).code, 'OK');
+
+			assert.ok(!e.matches(1024));
+			assert.ok(!e.matches('abc'));
 		},
-		"should match bool type": function() {
+
+		'Should match bool type.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('',8 );
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches(true) );
-			assert.ok( ! e.matches(8) );
+			assert.strictEqual(e.init('', 8).code, 'OK');
 
+			assert.ok(e.matches(true));
+			assert.ok(!e.matches(8));
 		},
-		"should match number type": function() {
+
+		'Should match number type.': function () {
 			var e = new TypeMatchExpression();
 			var s = e.init('a',1 );
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':[4]}) );	
-			assert.ok( e.matches({'a':[4, 'a']}) );	
-			assert.ok( e.matches({'a':['a', 4]}) );
-			assert.ok( ! e.matches({'a':['a']}) );
-			assert.ok( ! e.matches({'a':[[4]]}) );
+			assert.strictEqual(e.init('a', 1).code, 'OK');
+
+			assert.ok(e.matches({'a':[4]}));
+			assert.ok(e.matches({'a':[4, 'a']}));
+			assert.ok(e.matches({'a':['a', 4]}));
+			assert.ok(!e.matches({'a':['a']}));
+			assert.ok(!e.matches({'a':[[4]]}));
 
 		},
-		"should match array type": function() {
+
+		'Should match array type.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('a', 4);
-		
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[]}) );	
-			//assert.ok( ! e.matches({'a':[4, 'a']}) );
-			assert.ok( e.matches({'a':[[2]]}) );
-			assert.ok( ! e.matches({'a':'bar'}) );
 
+			assert.strictEqual(e.init('a', 4).code, 'OK');
+
+			assert.ok(!e.matches({a: []}));
+			assert.ok(e.matches({a: [[2]]}));
+			assert.ok(!e.matches({a: 'bar'}));
 		},
-		"should match null type more": function() {
+
+		'Should match null type expanded.': function () {
 			var e = new TypeMatchExpression();
-			var s = e.init('a', 10);
 
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':null}) );
-			assert.ok( ! e.matches({'a':4}) );
-			assert.ok( ! e.matches({}));
+			assert.strictEqual(e.init('a', 10).code, 'OK');
+
+			assert.ok(e.matches({a: null}));
+			assert.ok(!e.matches({a: 4}));
+			assert.ok(!e.matches({}));
 
 		},
-		"should match and preserve elemMatchKey": function() {
-			var e = new TypeMatchExpression();
-			var s = e.init('a.b', 2);
-			var m = new MatchDetails();
+
+		'Should match and preserve elemMatchKey.': function () {
+			var e = new TypeMatchExpression(),
+				m = new MatchDetails();
+
 			m.requestElemMatchKey();
 
-			assert.strictEqual(s.code, 'OK');
+			assert.strictEqual(e.init('a.b', 2).code, 'OK');
 			
-			assert.ok( ! e.matches({'a':1}, m) );
-			assert.ok( ! m.hasElemMatchKey() );
+			assert.ok(!e.matches({a: 1}, m));
+			assert.ok(!m.hasElemMatchKey());
 			
-			assert.ok( e.matches({'a':{'b':'string'}},m) );
-			assert.ok( ! m.hasElemMatchKey() );
+			assert.ok(e.matches({a: {b: 'string'}}, m));
+			assert.ok(!m.hasElemMatchKey());
 			
-			assert.ok( e.matches({'a':{'b':['string']}},m) );
-			assert.ok( m.hasElemMatchKey() );
-			assert.strictEqual('0', m.elemMatchKey() );
+			assert.ok(e.matches({a: {b: ['string']}}, m));
+			assert.ok(m.hasElemMatchKey());
+			assert.strictEqual('0', m.elemMatchKey());
 
-	
-			assert.ok( e.matches({'a':[2, {'b':['string']}]},m) );
-			assert.ok( m.hasElemMatchKey() );
-			assert.strictEqual('1', m.elemMatchKey() );
+			assert.ok(e.matches({a: [2, {b: ['string']}]}, m));
+			assert.ok(m.hasElemMatchKey());
 
+			assert.strictEqual('1', m.elemMatchKey());
+		},
 
+		'Should be equivalent.': function() {
+			var a = new TypeMatchExpression(),
+				b = new TypeMatchExpression(),
+				c = new TypeMatchExpression();
 
-		},
-		"should be equivalent": function() {
-			var e = new TypeMatchExpression();
-			var s = e.init('a', 2);
-			var b = new TypeMatchExpression();
-			var c = new TypeMatchExpression();
-
-			assert.strictEqual(s.code, 'OK');
-			s = b.init('a', 1);	
-			assert.strictEqual(s.code, 'OK');
-			s = c.init('b', 2);
-			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.equivalent(e) );
-			assert.ok( !e.equivalent(b) );
-			assert.ok( !e.equivalent(c) );
-		}
+			assert.strictEqual(a.init('a', 2).code, 'OK');
+			assert.strictEqual(b.init('a', 1).code, 'OK');
+			assert.strictEqual(c.init('b', 2).code, 'OK');
 
+			assert.ok(a.equivalent(a));
+			assert.ok(!a.equivalent(b));
+			assert.ok(!a.equivalent(c));
+		}
 	}
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-