Browse Source

Merge branch 'feature/mongo_2.6.5_matcher' into feature/mongo_2.6.5_matcher_RegexMatch

Phil Murray 11 years ago
parent
commit
97ae9d629d

+ 1 - 1
lib/pipeline/matcher/ElemMatchObjectMatchExpression.js

@@ -62,7 +62,7 @@ proto.matchesArray = function matchesArray(anArray, details){
 		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);
 			}

+ 45 - 50
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,29 @@ 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(){
+	debugger
+	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 +110,8 @@ proto.resetOutput = function resetOutput(){
  *
  */
 proto.setElemMatchKey = function setElemMatchKey(elemMatchKey){
-	// File: match_details.cpp lines: 46-49
+	debugger
 	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("    ");
+};
+
+
 

+ 108 - 49
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,9 @@ var errors = require("../../Errors.js"),
 	AllElemMatchOp = require("./AllElemMatchOp.js"),
 	AtomicMatchExpression = require("./AtomicMatchExpression.js");
 
+// 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 +42,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 +53,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 +108,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 +117,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 +126,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);
@@ -135,7 +161,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 +198,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 +226,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 +274,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 +310,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 +327,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 +368,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 +395,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 +402,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 +429,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 +449,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 +477,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 +492,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 +501,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 +526,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 +557,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 +597,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 +619,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 +671,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 +698,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 +735,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 +762,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 +773,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 +790,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);
 };

+ 2 - 2
lib/pipeline/matcher/Matcher2.js

@@ -265,10 +265,10 @@ proto.matches = function matches(doc, details){
 		return true;
 
 	if (this._indexKey == {})
-		return this._expression.matchesBSON(doc, details);
+		return this._expression.matchesJSON(doc, details);
 
 	if ((doc != {}) && (Object.keys(doc)[0]))
-		return this._expression.matchesBSON(doc, details);
+		return this._expression.matchesJSON(doc, details);
 
 	var mydoc = new IndexKeyMatchableDocument(this._indexKey, doc);
 	return this._expression.matches(mydoc, details);

+ 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());

+ 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 - 33
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,8 +588,6 @@ module.exports = {
 			assert.ok( ! res.result.matches({'x':'ABC'}) );
 			assert.ok( res.result.matches({'x':'AC'}) );
 		}
-
-
 	}
 };
 

+ 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);

+ 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());
-		
-			
+
+
 		}