Переглянути джерело

Fixes #1002. Finished MatchDocumentSource. Uses sift internally, so it isn't perfect.

http://source.rd.rcg.local/trac/eagle6/changeset/1329/Eagle6_SVN
Spencer Rathbun 12 роки тому
батько
коміт
4b79541feb

+ 123 - 0
lib/pipeline/documentSources/MatchDocumentSource.js

@@ -0,0 +1,123 @@
+var MatchDocumentSource = module.exports = (function(){
+	// CONSTRUCTOR
+	/**
+	 * A match document source built off of FilterBaseDocumentSource
+	 * Currently uses sift to fake it
+	 * 
+	 * @class MatchDocumentSource
+	 * @namespace munge.pipepline.documentsource
+	 * @module munge
+	 * @constructor
+	 * @param {Object} query the match query to use
+	**/
+	var klass = module.exports = MatchDocumentSource = function MatchDocumentSource(query /*, pCtx*/){
+		if(arguments.length !== 1) throw new Error("one arg expected");
+		base.call(this);
+		this.matcher = sift(query);
+		this.matchName = "$match";
+	}, base = require('./FilterBaseDocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+
+	proto.extend = function extend(obj, withObj){
+		var args = Array.prototype.slice.call(arguments), lastArg = args[args.length - 1]; 
+		for(var i = 1, n = args.length; i < n; i++){
+			withObj = args[i];
+			for(var key in withObj){
+				if(withObj.hasOwnProperty(key)){
+					var objVal = obj[key], withObjVal = withObj[key];
+					if(objVal instanceof Object && withObjVal.constructor === Object){
+						Object.extend(objVal, withObjVal, isRecursive);
+					}else{
+						obj[key] = withObjVal;
+					}
+				}
+			}
+		}   
+		return obj;
+	};
+
+	// DEPENDENCIES
+	var sift = require("sift");
+
+	proto.getSourceName = function getSourceName(){
+		return this.matchName;
+	};
+
+    /**
+     * Create an object that represents the document source.  The object
+     * will have a single field whose name is the source's name.  This
+     * will be used by the default implementation of addToJsonArray()
+     * to add this object to a pipeline being represented in JSON.
+     * 
+     * @method	sourceToJson
+     * @param	{Object} builder	JSONObjBuilder: a blank object builder to write to
+     * @param	{Boolean}	explain	create explain output
+    **/
+	proto.sourceToJson = function sourceToJson(builder, explain) {
+		builder[this.matchName] = this.matcher.query;
+	};
+
+	/**
+	* Test the given document against the predicate and report if it
+	* should be accepted or not.
+	*
+	* @param {object} document the document to test
+	* @returns {bool} true if the document matches the filter, false otherwise
+	**/
+	proto.accept = function accept(document) {
+		/**
+		* The matcher only takes BSON documents, so we have to make one.
+		*
+		* LATER
+		* We could optimize this by making a document with only the
+		* fields referenced by the Matcher.  We could do this by looking inside
+		* the Matcher's BSON before it is created, and recording those.  The
+		* easiest implementation might be to hold onto an ExpressionDocument
+		* in here, and give that pDocument to create the created subset of
+		* fields, and then convert that instead.
+		**/
+		return this.matcher.test(document);
+	};
+
+	/**
+	* Create a JSONObj suitable for Matcher construction.
+	*
+	* This is used after filter analysis has moved as many filters to
+	* as early a point as possible in the document processing pipeline.
+	* See db/Matcher.h and the associated wiki documentation for the
+	* format.  This conversion is used to move back to the low-level
+	* find() Cursor mechanism.
+	*
+	* @param builder the builder to write to
+	**/
+	proto.toMatcherJson = function toMatcherJson(builder) {
+		this.extend(builder, this.matcher.query);
+	};
+
+	proto.uassertNoDisallowedClauses = function uassertNoDisallowedClauses(query) {
+		for(var key in query){
+			if(query.hasOwnProperty(key)){
+				// can't use the Matcher API because this would segfault the constructor
+				if ( query[key] == "$where") throw new Error("code 16395; $where is not allowed inside of a $match aggregation expression");
+				// geo breaks if it is not the first portion of the pipeline
+				if ( query[key] == "$near") throw new Error("code 16424; $near is not allowed inside of a $match aggregation expression");
+				if ( query[key] == "$within") throw new Error("code 16425l $within is not allowed inside of a $match aggregation expression");
+				if ( query[key] == "$nearSphere") throw new Error("code 16426; $nearSphere is not allowed inside of a $match aggregation expression");
+
+				if(query[key] instanceof Object && query[key].constructor === Object)
+					this.uassertNoDisallowedClauses(query[key]);
+			}
+		}
+	}; 
+
+	proto.createFromJson = function createFromJson(JsonElement) {
+		if (!(JsonElement instanceof Object) || JsonElement.constructor !== Object) throw new Error("code 15959 ; the match filter must be an expression in an object");
+
+		this.uassertNoDisallowedClauses(JsonElement);
+
+		var matcher = new MatchDocumentSource(JsonElement);
+
+		return matcher;
+	};
+
+	return klass;
+})();

+ 2 - 1
package.json

@@ -24,7 +24,8 @@
 	],
 	"dependencies": {
 		"stream-utils":"*",
-		"es6-shim":"*"
+		"es6-shim":"*",
+		"sift":"*"
 	},
 	"devDependencies": {
 		"mocha": "*",

+ 3 - 3
test/lib/pipeline/documentSources/FilterBaseDocumentSource.js

@@ -1,7 +1,7 @@
 var assert = require("assert"),
 	FilterBaseDocumentSource = require("../../../../lib/pipeline/documentSources/FilterBaseDocumentSource");
 
-function createAccumulator(){
+function createSource(){
 	var fbds = new FilterBaseDocumentSource();
 	fbds.addOperand(new FieldPathExpression("a") );
 	return fbds;
@@ -28,10 +28,10 @@ module.exports = {
 
 		"#accept()": {
 
-			"should throw Error when calling toMatcherJson": function testConstructor(){
+			"should throw Error when calling accept": function testConstructor(){
 				assert.throws(function(){
 					var fbds = new FilterBaseDocumentSource();
-					fbds.toMatcherJson();
+					fbds.accept();
 				});
 			}
 

+ 76 - 0
test/lib/pipeline/documentSources/MatchDocumentSource.js

@@ -0,0 +1,76 @@
+var assert = require("assert"),
+	MatchDocumentSource = require("../../../../lib/pipeline/documentSources/MatchDocumentSource");
+
+module.exports = {
+
+	"MatchDocumentSource": {
+
+		"constructor()": {
+
+			"should throw Error when constructing without args": function testConstructor(){
+				assert.throws(function(){
+					new MatchDocumentSource();
+				});
+			}
+
+		},
+
+		"#getSourceName()": {
+
+			"should return the correct source name; $match": function testSourceName(){
+				var mds = new MatchDocumentSource({ packet :{ $exists : false } });
+				assert.strictEqual(mds.getSourceName(), "$match");
+			}
+
+		},
+
+		"#accept()": {
+
+			"should return true on the input document": function acceptTest(){
+				var mds = new MatchDocumentSource({ location : { $in : ['Kentucky'] } });
+				assert.strictEqual(mds.accept({ name: 'Adam', location: 'Kentucky'}), true);
+			}
+
+		},
+
+		"#sourceToJson()": {
+
+			"should append the match query to the input builder": function sourceToJsonTest(){
+				var mds = new MatchDocumentSource({ location : { $in : ['Kentucky'] } });
+				var t = {};
+				mds.sourceToJson(t, false);
+				assert.deepEqual(t, { "$match" : { location : { $in : ['Kentucky'] } }});
+			}
+
+		},
+
+		"#toMatcherJson()": {
+
+			"should append the match query to an object suitable for creating a new matcher": function convertTest(){
+				var mds = new MatchDocumentSource({ location : { $in : ['Kentucky'] } });
+				var t = {};
+				mds.toMatcherJson(t);
+				assert.deepEqual(t, { location : { $in : ['Kentucky'] } });
+			}
+
+
+		},
+
+		"#createFromJson()": {
+
+			"should return a new MatchDocumentSource object from an input object": function createTest(){
+				var mds = new MatchDocumentSource({ location : { $in : ['Kentucky'] } });
+				var t = mds.createFromJson({ someval:{$exists:true} });
+				assert.strictEqual(t instanceof MatchDocumentSource, true);
+			}
+
+
+		}
+
+
+	}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+