Sfoglia il codice sorgente

EAGLESIX-2653: Code conversion of MatchExpressionParser

TODO:
* One failing test case
* Need to comb over test cases and port anything new over
* A few derivations need to be examined
Chris Sexton 11 anni fa
parent
commit
fc782b055d

+ 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 kMaximumTreeDepth = 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 (var i = 0; i < length; i++) {
+		var 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 > kMaximumTreeDepth)
+		return {code:ErrorCodes.BAD_VALUE, description:"exceeded maximum query tree depth of " +
+			kMaximumTreeDepth + " at " + 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.BadValue, 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 > kMaximumTreeDepth) {
+		return {code:ErrorCodes.BadValue, description:"exceeded maximum query tree depth of " +
+			kMaximumTreeDepth + " at " + 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.BadValue, 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);
 };

+ 20 - 42
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]}};
@@ -308,18 +297,7 @@ module.exports = {
 			var q = {'x':{'$in':[{'$elemMatch': 1}]}};
 
 			var res = parser.parse( q );
-			assert.strictEqual( res.code, 'BAD_VALUE' );
-		},
-		"Should not parse regexes that are too long": function() {
-			var parser = new MatchExpressionParser();
-			var str = (new Array(50*1000+1).join('z'));
-			var q = {'x': {'$in':[new RegExp(str)]}};
-
-			var res = parser.parse( q );
-			assert.strictEqual( res.code, 'BAD_VALUE' );
-	
-			q = {'x':{'$in': [{'$regex': str}]}};
-			res = parser.parse( q );
+			// TODO: busted
 			assert.strictEqual( res.code, 'BAD_VALUE' );
 		},
 		"Should parse and match $regex in an $in expression": function() {
@@ -392,11 +370,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 +400,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 +423,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 +457,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 +481,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}) );