Browse Source

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

Conflicts:
	test/lib/pipeline/matcher/GTEMatchExpression.js
	test/lib/pipeline/matcher/GTMatchExpression.js
	test/lib/pipeline/matcher/LTEMatchExpression.js
	test/lib/pipeline/matcher/LTMatchExpression.js
Jared Hall 11 years ago
parent
commit
845bb9b59f

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

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

+ 7 - 3
lib/pipeline/matcher/ElementPath.js

@@ -158,8 +158,7 @@ proto._matches = function _matches(doc, details, checker) {
  * @param function checker this function is used to check for a valid item at the end of the path
  *
  */
-klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details, checker) {
-	// File: expression_array.cpp lines: 34-53
+klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details, checker){
 	var k, result, ii, il,
 		curr = doc,
 		item = doc;
@@ -205,7 +204,7 @@ klass._matches = function _matches(doc, path, shouldTraverseLeafArray, details,
 			// otherwise, check each item in the array against the rest of the path
 			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());
@@ -213,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;
 };
-

+ 9 - 21
lib/pipeline/matcher/InMatchExpression.js

@@ -38,17 +38,8 @@ proto._matchesRealElement = function _matchesRealElement(e) {
 	}
 
 	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.
 	}
 
 	return false;
@@ -62,20 +53,19 @@ proto._matchesRealElement = function _matchesRealElement(e) {
  *
  */
 proto.matchesSingleElement = function matchesSingleElement(e) {
-	if( this._arrayEntries === null && typeof(e) == 'object' && Object.keys(e).length === 0) {
+	if( this._arrayEntries.hasNull() && 
+		(	e === null ||
+			e === undefined ||
+			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;
 };
 
@@ -150,5 +140,3 @@ proto.getArrayFilterEntries = function getArrayFilterEntries(){
 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);
-};

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

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

+ 2 - 1
package.json

@@ -27,7 +27,8 @@
     "alteration"
   ],
   "dependencies": {
-    "async": "*"
+    "async": "*",
+    "xregexp": "*"
   },
   "devDependencies": {
     "mocha": "*",

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

+ 5 - 25
test/lib/pipeline/matcher/GTEMatchExpression.js

@@ -8,13 +8,13 @@ module.exports = {
 	"GTEMatchExpression": {
 		"should match scalars and strings properly": function (){
 			var e = new GTEMatchExpression();
-			var s = e.init('a',5);
+			var s = e.init('x',5);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matches({'a':5.5}) );
-			assert.ok( e.matches({'a':5}) );
-			assert.ok( ! e.matches({'a':4}) );
-			assert.ok( ! e.matches({'a': 'foo'}) );
+			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 handle invalid End of Object Operand": function testInvalidEooOperand(){
 			var e = new GTEMatchExpression();
@@ -43,8 +43,6 @@ module.exports = {
 			var s = e.init('a',[5]);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matches({'a':[4]}) );
-			assert.ok( e.matches({'a':[5]}) );
 			assert.ok( e.matches({'a':[6]}) );
 		},
 		"should not match null" : function() {
@@ -55,24 +53,6 @@ module.exports = {
 			assert.ok( e.matches({}) );
 			assert.ok( e.matches({'a':null}) );
 			assert.ok( ! e.matches({'a':4}) );
-			assert.ok( e.matches({'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() {
-		},
-		"should match MaxKey": function() {
 		},
 		"should handle elemMatchKey":function() {
 			var e = new GTEMatchExpression();

+ 59 - 84
test/lib/pipeline/matcher/GTMatchExpression.js

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

+ 60 - 101
test/lib/pipeline/matcher/LTEMatchExpression.js

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

+ 28 - 70
test/lib/pipeline/matcher/LTMatchExpression.js

@@ -1,24 +1,20 @@
 "use strict";
 var assert = require("assert"),
-	BSON = require("bson"),
 	MatchDetails = require('../../../../lib/pipeline/matcher/MatchDetails'),
 	LTMatchExpression = require("../../../../lib/pipeline/matcher/LTMatchExpression");
 
-// 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 = {
 	"LTMatchExpression": {
 		"should match scalars and strings properly": function (){
 			var e = new LTMatchExpression();
-			var s = e.init('a',5);
-
+			var s = e.init('x',5);
+			
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( ! e.matchesJSON({'a':5}) );
-			assert.ok( e.matchesJSON({'a':4.5}) );
-			assert.ok( ! e.matchesJSON({'a':6}) );
-			assert.ok( ! e.matchesJSON({'a':5}) );
-			assert.ok( ! e.matchesJSON({'a': 'foo'}) );
+			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 handle invalid End of Object Operand": function testInvalidEooOperand(){
 			var e = new LTMatchExpression();
@@ -26,53 +22,45 @@ module.exports = {
 
 			assert.strictEqual(s.code, 'BAD_VALUE');
 		},
-		"should match a scalar":function() {
+		"should match a pathed number":function() {
 			var e = new LTMatchExpression();
 			var s = e.init('a',5);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matchesJSON({'a':4.5}) );
-			assert.ok( ! e.matchesJSON({'a':6}) );
+			assert.ok( e.matches({'a':4.5}) );
+			assert.ok( ! e.matches({'a':6}) );
 		},
-		"should match an empty key":function() {
+		"should match an empty pathed number":function() {
 			var e = new LTMatchExpression();
 			var s = e.init('',5);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matchesJSON({'':4.5}) );
-			assert.ok( ! e.matchesJSON({'':6}) );
+			assert.ok( e.matches({'':4.5}) );
+			assert.ok( ! e.matches({'':6}) );
 		},
-		"should match array values": function() {
+		"should match stuff in an array": function() {
 			var e = new LTMatchExpression();
 			var s = e.init('a',5);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok( e.matchesJSON({'a':[6,4.5]}) );
-			assert.ok( ! e.matchesJSON({'a':[6,7]}) );
+			assert.ok( e.matches({'a':[6,4.5]}) );
+			assert.ok( ! e.matches({'a':[6,7]}) );
 		},
-		"should match whole array" : function() {
+		"should not match full array" : function() {
 			var e = new LTMatchExpression();
 			var s = e.init('a',[5]);
 
 			assert.strictEqual(s.code, 'OK');
-			assert.ok(e.matchesJSON({'a':[4]}));
-			assert.ok(!e.matchesJSON({'a':[5]}));
-			assert.ok(!e.matchesJSON({'a':[6]}));
-			// Nested arrays
-			assert.ok(!e.matchesJSON({'a':[[4]]}));
-			assert.ok(!e.matchesJSON({'a':[[5]]}));
-			assert.ok(!e.matchesJSON({'a':[[6]]}));
+			assert.ok( e.matches({'a':[4]}) );
 		},
 		"should not match null" : function() {
 			var e = new LTMatchExpression();
 			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}) );
-			// A non-existent field is treated same way as an empty bson object
-			assert.ok( ! e.matchesJSON({'b':4}) );
+			assert.ok( ! e.matches({}) );
+			assert.ok( ! e.matches({'a':null}) );
+			assert.ok( ! e.matches({'a':4}) );
 		},
 		"should handle elemMatchKey":function() {
 			var e = new LTMatchExpression();
@@ -81,51 +69,21 @@ module.exports = {
 			m.requestElemMatchKey();
 			assert.strictEqual( s.code, 'OK' );
 
-			assert.ok( ! e.matchesJSON({'a':6}, m) );
+			assert.ok( ! e.matches({'a':6}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matchesJSON({'a':4}, m) );
+			assert.ok( e.matches({'a':4}, m) );
 			assert.ok( ! m.hasElemMatchKey() );
 
-			assert.ok( e.matchesJSON({'a':[6,2,5]}, m));
+			assert.ok( e.matches({'a':[6,2,5]}, m));
 			assert.ok( m.hasElemMatchKey());
 			assert.strictEqual('1', m.elemMatchKey());
-		},
-		"should match dot notation with nulls": function() {
-			var e = new LTMatchExpression();
-			var s = e.init('a.b',null);
-
-			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 e = new LTMatchExpression();
-			var s = e.init('a', new BSON.MinKey());
+		}
 
-			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 e = new LTMatchExpression();
-			var s = e.init('a', new BSON.MaxKey());
 
-			assert.ok(!e.matchesJSON({a: new BSON.MaxKey()}));
-			assert.ok(e.matchesJSON({a: new BSON.MinKey()}));
-			assert.ok(e.matchesJSON({a: 4}));
-		},
-		"should match with ElemMatchKey": function() {
-			var e = new LTMatchExpression()
-			var s = e.init('a', 5);
 
-			assert.ok(!e.matchesJSON({a: 6}));
-			assert.ok(e.matchesJSON({a: 4}));
-			assert.ok(e.matchesJSON({a: [6,2,5]}));
-		},
 	}
 };
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+

+ 0 - 1
test/lib/pipeline/matcher/MatchExpressionParser.js

@@ -592,4 +592,3 @@ module.exports = {
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
-

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