瀏覽代碼

Merge branch 'feature/mongo_2.6.5_documentSource' into feature/mongo_2.6.5_documentSource_Out

Jason Walton 11 年之前
父節點
當前提交
875e87e5e3

+ 27 - 13
lib/pipeline/documentSources/LimitDocumentSource.js

@@ -10,10 +10,10 @@ var DocumentSource = require('./DocumentSource');
  * @constructor
  * @param [ctx] {ExpressionContext}
  **/
-var LimitDocumentSource = module.exports = function LimitDocumentSource(ctx){
-	if (arguments.length > 1) throw new Error("up to one arg expected");
+var LimitDocumentSource = module.exports = function LimitDocumentSource(ctx, limit){
+	if (arguments.length > 2) throw new Error("up to two args expected");
 	base.call(this, ctx);
-	this.limit = 0;
+	this.limit = limit;
 	this.count = 0;
 }, klass = LimitDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
@@ -44,9 +44,17 @@ proto.coalesce = function coalesce(nextSource) {
 	return true;
 };
 
+/* Returns the execution of the callback against
+* the next documentSource
+* @param {function} callback
+* @return {bool} indicating end of document reached
+*/
 proto.getNext = function getNext(callback) {
 	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
 
+	if (this.expCtx instanceof Object && this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
+		return callback(new Error("Interrupted"));
+
 	if (++this.count > this.limit) {
 		this.source.dispose();
 		callback(null, DocumentSource.EOF);
@@ -57,19 +65,25 @@ proto.getNext = function getNext(callback) {
 };
 
 /**
- * Creates a new LimitDocumentSource with the input number as the limit
- * @param {Number} JsonElement this thing is *called* Json, but it expects a number
- **/
-klass.createFromJson = function createFromJson(jsonElement, ctx) {
-	if (typeof jsonElement !== "number") throw new Error("code 15957; the limit must be specified as a number");
+Create a limiting DocumentSource from JSON.
 
-	var Limit = proto.getFactory(),
-		nextLimit = new Limit(ctx);
+This is a convenience method that uses the above, and operates on
+a JSONElement that has been deteremined to be an Object with an
+element named $limit.
 
-	nextLimit.limit = jsonElement;
-	if ((nextLimit.limit <= 0) || isNaN(nextLimit.limit)) throw new Error("code 15958; the limit must be positive");
+@param jsonElement the JSONELement that defines the limit
+@param ctx the expression context
+@returns the grouping DocumentSource
+*/
+klass.createFromJson = function createFromJson(jsonElement, ctx) {
+	if (typeof jsonElement !== "number") throw new Error("code 15957; the limit must be specified as a number");
+	var limit = jsonElement;
+	return this.create(ctx, limit);
+};
 
-	return nextLimit;
+klass.create = function create(ctx, limit){
+	if ((limit <= 0) || isNaN(limit)) throw new Error("code 15958; the limit must be positive");
+	return new LimitDocumentSource(ctx, limit);
 };
 
 proto.getLimit = function getLimit(newLimit) {

+ 34 - 4
lib/pipeline/documentSources/MatchDocumentSource.js

@@ -22,6 +22,13 @@ var MatchDocumentSource = module.exports = function MatchDocumentSource(query, c
 	base.call(this, ctx);
 	this.query = query; // save the query, so we can check it for deps later. THIS IS A DEVIATION FROM THE MONGO IMPLEMENTATION
 	this.matcher = new matcher(query);
+
+	// not supporting currently $text operator
+	// set _isTextQuery to false.
+	// TODO: update after we implement $text.
+	if (klass.isTextQuery(query)) throw new Error("$text pipeline operation not supported");
+	this._isTextQuery = false;
+
 }, klass = MatchDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 klass.matchName = "$match";
@@ -33,6 +40,10 @@ proto.getSourceName = function getSourceName(){
 proto.getNext = function getNext(callback) {
 	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
 
+	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false) {
+		return callback(new Error('Interrupted'));
+	}
+
 	var self = this,
 		next,
 		test = function test(doc) {
@@ -86,11 +97,11 @@ klass.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");
+			if (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 16425; $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 (key === "$near") throw new Error("code 16424; $near is not allowed inside of a $match aggregation expression");
+			if (key === "$within") throw new Error("code 16425; $within is not allowed inside of a $match aggregation expression");
+			if (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]);
 		}
 	}
@@ -103,6 +114,25 @@ klass.createFromJson = function createFromJson(jsonElement, ctx) {
 	return matcher;
 };
 
+proto.isTextQuery = function isTextQuery() {
+    return this._isTextQuery;
+};
+
+klass.isTextQuery = function isTextQuery(query) {
+    for (var key in query) {
+        var fieldName = key;
+        if (fieldName === "$text") return true;
+        if (query[key] instanceof Object && query[key].constructor === Object && this.isTextQuery(query[key])) {
+            return true;
+        }
+    }
+    return false;
+};
+
+klass.setSource = function setSource (source) {
+	this.setSource(source);
+};
+
 proto.getQuery = function getQuery() {
 	return this.matcher._pattern;
 };

+ 28 - 26
test/lib/pipeline/documentSources/LimitDocumentSource.js

@@ -10,74 +10,80 @@ module.exports = {
 
 		"constructor()": {
 
-			"should not throw Error when constructing without args": function testConstructor(){
+			"should not throw Error when constructing without args": function testConstructor(next){
 				assert.doesNotThrow(function(){
 					new LimitDocumentSource();
+					return next();
 				});
 			}
-
 		},
 
+ 		/** A limit does not introduce any dependencies. */
 		"#getDependencies": {
-			"limits do not create dependencies": function() {
-				var lds = LimitDocumentSource.createFromJson(1),
+			"limits do not create dependencies": function(next) {
+				var lds = LimitDocumentSource.createFromJson(1, null),
 					deps = {};
 
 				assert.equal(DocumentSource.GetDepsReturn.SEE_NEXT, lds.getDependencies(deps));
 				assert.equal(0, Object.keys(deps).length);
+				return next();
 			}
 		},
 
 		"#getSourceName()": {
 
-			"should return the correct source name; $limit": function testSourceName(){
+			"should return the correct source name; $limit": function testSourceName(next){
 				var lds = new LimitDocumentSource();
 				assert.strictEqual(lds.getSourceName(), "$limit");
+				return next();
 			}
-
 		},
 
 		"#getFactory()": {
 
-			"should return the constructor for this class": function factoryIsConstructor(){
+			"should return the constructor for this class": function factoryIsConstructor(next){
 				assert.strictEqual(new LimitDocumentSource().getFactory(), LimitDocumentSource);
+				return next();
 			}
-
 		},
 
 		"#coalesce()": {
 
-			"should return false if nextSource is not $limit": function dontSkip(){
+			"should return false if nextSource is not $limit": function dontSkip(next){
 				var lds = new LimitDocumentSource();
 				assert.equal(lds.coalesce({}), false);
+				return next();
 			},
-			"should return true if nextSource is $limit": function changeLimit(){
+			"should return true if nextSource is $limit": function changeLimit(next){
 				var lds = new LimitDocumentSource();
 				assert.equal(lds.coalesce(new LimitDocumentSource()), true);
+				return next();
 			}
-
 		},
 
 		"#getNext()": {
 
-			"should throw an error if no callback is given": function() {
+			"should throw an error if no callback is given": function(next) {
 				var lds = new LimitDocumentSource();
 				assert.throws(lds.getNext.bind(lds));
+				return next();
 			},
 
+			/** Exhausting a DocumentSourceLimit disposes of the limit's source. */
 			"should return the current document source": function currSource(next){
-				var lds = new LimitDocumentSource();
+				var lds = new LimitDocumentSource({"$limit":[{"a":1},{"a":2}]});
 				lds.limit = 1;
 				lds.source = {getNext:function(cb){cb(null,{ item:1 });}};
 				lds.getNext(function(err,val) {
 					assert.deepEqual(val, { item:1 });
-					next();
+					return next();
 				});
 			},
 
+			/** Exhausting a DocumentSourceLimit disposes of the pipeline's DocumentSourceCursor. */
 			"should return EOF for no sources remaining": function noMoar(next){
-				var lds = new LimitDocumentSource();
-				lds.limit = 10;
+				var lds = new LimitDocumentSource({"$match":[{"a":1},{"a":1}]});
+				lds.limit = 1;
 				lds.source = {
 					calls: 0,
 					getNext:function(cb) {
@@ -91,7 +97,7 @@ module.exports = {
 				lds.getNext(function(){});
 				lds.getNext(function(err,val) {
 					assert.strictEqual(val, DocumentSource.EOF);
-					next();
+					return next();
 				});
 			},
 
@@ -110,36 +116,32 @@ module.exports = {
 				lds.getNext(function(){});
 				lds.getNext(function (err,val) {
 					assert.strictEqual(val, DocumentSource.EOF);
-					next();
+					return next();
 				});
 			}
-
 		},
 
 		"#serialize()": {
 
-			"should create an object with a key $limit and the value equal to the limit": function sourceToJsonTest(){
+			"should create an object with a key $limit and the value equal to the limit": function sourceToJsonTest(next){
 				var lds = new LimitDocumentSource();
 				lds.limit = 9;
 				var actual = lds.serialize(false);
 				assert.deepEqual(actual, { "$limit": 9 });
+				return next();
 			}
-
 		},
 
 		"#createFromJson()": {
 
-			"should return a new LimitDocumentSource object from an input number": function createTest(){
+			"should return a new LimitDocumentSource object from an input number": function createTest(next){
 				var t = LimitDocumentSource.createFromJson(5);
 				assert.strictEqual(t.constructor, LimitDocumentSource);
 				assert.strictEqual(t.limit, 5);
+				return next();
 			}
-
 		}
-
-
 	}
-
 };
 
 if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

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

@@ -20,6 +20,12 @@ module.exports = {
 				assert.throws(function(){
 					new MatchDocumentSource();
 				});
+			},
+
+			"should throw Error when trying to using a $text operator": function testTextOp () {
+				assert.throws(function(){
+					new MatchDocumentSource({packet:{ $text:"thisIsntImplemented" } });
+				});
 			}
 
 		},
@@ -353,6 +359,87 @@ module.exports = {
 					{});
 			}
 
+		},
+
+		"#isTextQuery()": {
+
+			"should return true when $text operator is first stage in pipeline": function () {
+				var query = {$text:'textQuery'}
+				assert.ok(MatchDocumentSource.isTextQuery(query)); // true
+			},
+
+			"should return true when $text operator is nested in the pipeline": function () {
+				var query = {$stage:{$text:'textQuery'}};
+				assert.ok(MatchDocumentSource.isTextQuery(query)); // true
+			},
+
+			"should return false when $text operator is not in pipeline": function () {
+				var query = {$notText:'textQuery'}
+				assert.ok(!MatchDocumentSource.isTextQuery(query)); // false
+			}
+
+		},
+
+		"#uassertNoDisallowedClauses()": {
+
+			"should throw if invalid stage is in match expression": function () {
+				var whereQuery = {$where:'where'};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
+				});
+
+				var nearQuery = {$near:'near'};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
+				});
+
+				var withinQuery = {$within:'within'};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
+				});
+
+				var nearSphereQuery = {$nearSphere:'nearSphere'};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
+				});
+			},
+
+			"should throw if invalid stage is nested in the match expression": function () {
+				var whereQuery = {$validStage:{$where:'where'}};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(whereQuery);
+				});
+
+				var nearQuery = {$validStage:{$near:'near'}};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(nearQuery);
+				});
+
+				var withinQuery = {$validStage:{$within:'within'}};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(withinQuery);
+				});
+
+				var nearSphereQuery = {$validStage:{$nearSphere:'nearSphere'}};
+				assert.throws(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(nearSphereQuery);
+				});
+			},
+
+			"should not throw if invalid stage is not in match expression": function () {
+				var query = {$valid:'valid'};
+				assert.doesNotThrow(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(query);
+				});
+			},
+
+			"should not throw if invalid stage is not nested in the match expression": function () {
+				var query = {$valid:{$anotherValid:'valid'}};
+				assert.doesNotThrow(function(){
+					MatchDocumentSource.uassertNoDisallowedClauses(query);
+				});
+			},
+
 		}
 
 	}