Explorar el Código

Merge pull request #89 from RiveraGroup/feature/mongo_2.6.5_matcher_RegexMatch

Feature/mongo 2.6.5 matcher regex match
Chris Sexton hace 11 años
padre
commit
d6d4161ed2

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

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

+ 2 - 1
package.json

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

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

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