Prechádzať zdrojové kódy

Implemented $skip document source. fixes #1007

http://source.rd.rcg.local/trac/eagle6/changeset/1338/Eagle6_SVN
Philip Murray 12 rokov pred
rodič
commit
656f814004

+ 3 - 3
lib/pipeline/Pipeline.js

@@ -8,9 +8,9 @@ var Pipeline = module.exports = (function(){
 	
 	var LimitDocumentSource = require('./documentSources/LimitDocumentSource'),
 		MatchDocumentSource = require('./documentSources/MatchDocumentSource'),
-		ProjectDocumentSource = require('./documentSources/ProjectDocumentSource');
+		ProjectDocumentSource = require('./documentSources/ProjectDocumentSource'),
+		SkipDocumentSource = require('./documentSources/SkipDocumentSource');
 //		GroupDocumentSource = require('./documentSources/GroupDocumentSource'),
-//		SkipDocumentSource = require('./documentSources/SkipDocumentSource'),
 //		SortDocumentSource = require('./documentSources/SortDocumentSource'),
 //		UnwindDocumentSource = require('./documentSources/UnwindDocumentSource');
 	
@@ -18,8 +18,8 @@ var Pipeline = module.exports = (function(){
 	klass.StageDesc[LimitDocumentSource.limitName] = LimitDocumentSource.createFromJson;
 	klass.StageDesc[MatchDocumentSource.matchName] = MatchDocumentSource.createFromJson;
 	klass.StageDesc[ProjectDocumentSource.projectName] = ProjectDocumentSource.createFromJson;
+	klass.StageDesc[SkipDocumentSource.skipName] = SkipDocumentSource.createFromJson;
 //	klass.StageDesc[GroupDocumentSource.groupName] = GroupDocumentSource.createFromJson;
-//	klass.StageDesc[SkipDocumentSource.skipName] = SkipDocumentSource.createFromJson;
 //	klass.StageDesc[SortDocumentSource.sortName] = SortDocumentSource.createFromJson;
 //	klass.StageDesc[UnwindDocumentSource.unwindName] = UnwindDocumentSource.createFromJson;
 	

+ 0 - 4
lib/pipeline/documentSources/LimitDocumentSource.js

@@ -25,10 +25,6 @@ var LimitDocumentSource = module.exports = (function(){
 		return klass;	// using the ctor rather than a separate .create() method
 	};
 
-	klass.GetDepsReturn = {
-        SEE_NEXT:"SEE_NEXT", // Add the next Source's deps to the set
-	};
-
 	/**
 	 * Coalesce limits together
 	 *

+ 140 - 0
lib/pipeline/documentSources/SkipDocumentSource.js

@@ -0,0 +1,140 @@
+var SkipDocumentSource = module.exports = (function(){
+	// CONSTRUCTOR
+	/**
+	 * A document source skipper
+	 * 
+	 * @class SkipDocumentSource
+	 * @namespace munge.pipepline.documentsource
+	 * @module munge
+	 * @constructor
+	 * @param {Object} query the match query to use
+	**/
+	var klass = module.exports = SkipDocumentSource = function SkipDocumentSource(/* pCtx*/){
+		if(arguments.length !== 0) throw new Error("zero args expected");
+		base.call(this);
+		this.skip = 0;
+		this.count = 0;
+	}, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+
+	klass.skipName = "$skip";
+	proto.getSourceName = function getSourceName(){
+		return klass.skipName;
+	};
+
+	/**
+	 * Coalesce skips together
+	 *
+	 * @param {Object} nextSource the next source
+	 * @return {bool} return whether we can coalese together
+	**/
+	proto.coalesce = function coalesce(nextSource) {
+		var nextSkip =	nextSource.constructor === SkipDocumentSource?nextSource:null;
+
+		/* if it's not another $skip, we can't coalesce */
+		if (!nextSkip)
+			return false;
+			
+        /* we need to skip over the sum of the two consecutive $skips */
+		this.skip += nextSkip.skip;
+		return true;
+	};
+
+	proto.skipper = function skipper() {
+		if (this.count === 0) {
+			while (!this.pSource.eof() && this.count++ < this.skip) {
+				this.pSource.advance();
+			}
+		}
+	
+		if (this.pSource.eof()) {
+			this.pCurrent = null;
+			return;
+		}
+	
+		this.pCurrent = this.pSource.getCurrent();
+	};
+
+
+    /**
+     * Is the source at EOF?
+     * 
+     * @method	eof
+    **/
+    proto.eof = function eof() {
+		this.skipper();
+		return this.pSource.eof();
+    };
+
+    /**
+     * some implementations do the equivalent of verify(!eof()) so check eof() first
+     * 
+     * @method	getCurrent
+     * @returns	{Document}	the current Document without advancing
+    **/
+    proto.getCurrent = function getCurrent() {
+		this.skipper();
+		return this.pSource.getCurrent();
+    };
+
+	/**
+	 * Advance the state of the DocumentSource so that it will return the next Document.  
+	 * The default implementation returns false, after checking for interrupts. 
+	 * Derived classes can call the default implementation in their own implementations in order to check for interrupts.
+	 * 
+	 * @method	advance
+	 * @returns	{Boolean}	whether there is another document to fetch, i.e., whether or not getCurrent() will succeed.  This default implementation always returns false.
+	**/
+	proto.advance = function advance() {
+		base.prototype.advance.call(this); // check for interrupts
+        if (this.eof()) {
+            this.pCurrent = null;
+            return false;
+        }
+
+        this.pCurrent = this.pSource.getCurrent();
+        return this.pSource.advance();
+
+	};
+
+	/**
+	 * 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.$skip = this.skip;
+	};
+
+	/**
+	 * Creates a new SkipDocumentSource with the input number as the skip
+	 *
+	 * @param {Number} JsonElement this thing is *called* Json, but it expects a number
+	**/
+	klass.createFromJson = function createFromJson(JsonElement) {
+		if (typeof JsonElement !== "number") throw new Error("code 15972; the value to skip must be a number");
+
+		var nextSkip = new SkipDocumentSource();
+
+		nextSkip.skip = JsonElement;
+		if ((nextSkip.skip < 0) || isNaN(nextSkip.skip)) throw new Error("code 15956; the number to skip cannot be negative");
+
+		return nextSkip;
+	};
+	
+    /**
+     * Reset the document source so that it is ready for a new stream of data.
+     * Note that this is a deviation from the mongo implementation.
+     * 
+     * @method	reset
+    **/
+	proto.reset = function reset(){
+		this.count = 0;
+	};
+
+	return klass;
+})();

+ 1 - 1
test/lib/munge.js

@@ -42,7 +42,6 @@ module.exports = {
 			assert.equal(JSON.stringify(munge(p, i)), JSON.stringify(e), "Alternate use of munge should yield the same results!");
 		},
 		
-/*
 		"should be able to use a $skip operator": function(){
 			var i = [{_id:0}, {_id:1}, {_id:2}, {_id:3}, {_id:4}, {_id:5}],
 				p = [{$skip:2}, {$skip:1}],	//testing w/ 2 ensures independent state variables
@@ -67,6 +66,7 @@ module.exports = {
 		},
 
 
+/*
 		"should be able to use a $project operator": function(){
 			var i = [{_id:0, e:1}, {_id:1, e:0}, {_id:2, e:1}, {_id:3, e:0}, {_id:4, e:1}, {_id:5, e:0}],
 				p = [{$project:{e:1}}],

+ 158 - 0
test/lib/pipeline/documentSources/SkipDocumentSource.js

@@ -0,0 +1,158 @@
+var assert = require("assert"),
+	SkipDocumentSource = require("../../../../lib/pipeline/documentSources/SkipDocumentSource");
+
+module.exports = {
+
+	"SkipDocumentSource": {
+
+		"constructor()": {
+
+			"should not throw Error when constructing without args": function testConstructor(){
+				assert.doesNotThrow(function(){
+					new SkipDocumentSource();
+				});
+			}
+
+		},
+
+		"#getSourceName()": {
+
+			"should return the correct source name; $skip": function testSourceName(){
+				var lds = new SkipDocumentSource();
+				assert.strictEqual(lds.getSourceName(), "$skip");
+			}
+
+		},
+
+		"#coalesce()": {
+
+			"should return false if nextSource is not $skip": function dontSkip(){
+				var lds = new SkipDocumentSource();
+				assert.equal(lds.coalesce({}), false);
+			},
+			"should return true if nextSource is $skip": function changeSkip(){
+				var lds = new SkipDocumentSource();
+				assert.equal(lds.coalesce(new SkipDocumentSource()), true);
+			}
+
+		},
+
+		"#eof()": {
+
+			"should return true if there are no more sources": function noSources(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 9;
+				lds.count = 0;
+				lds.pSource = {
+					eof: function(){
+						return true;
+					}
+				};
+				assert.equal(lds.eof(), true);
+			},
+			"should return false if skip count is not hit and there are more documents": function hitSkip(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 10;
+				lds.count = 9;
+				
+				var i = 1;
+				lds.pSource = {
+					getCurrent:function(){return { item:i };},
+					eof: function(){return false;},
+					advance: function(){i++; return true;}
+				};
+				assert.equal(lds.eof(), false);
+			}
+
+		},
+
+		"#getCurrent()": {
+
+			"should return the current document source": function currSource(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 1;
+				
+				var i = 0;
+				lds.pSource = {
+					getCurrent:function(){return { item:i };},
+					eof: function(){return false;},
+					advance: function(){i++; return true;}
+				};
+				assert.deepEqual(lds.getCurrent(), { item:1 });
+			}
+
+		},
+
+		"#advance()": {
+
+			"should return true for moving to the next source": function nextSource(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 1;
+				
+				var i = 0;
+				lds.pSource = {
+					getCurrent:function(){return { item:i };},
+					eof: function(){return false;},
+					advance: function(){i++; return true;}
+				};
+				assert.strictEqual(lds.advance(), true);
+			},
+
+			"should return false for no sources remaining": function noMoar(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 1;
+				
+				var i = 0;
+				lds.pSource = {
+					getCurrent:function(){return { item:i };},
+					eof: function(){return true;},
+					advance: function(){return false;}
+				};
+				assert.strictEqual(lds.advance(), false);
+			},
+
+			"should return false if we hit our limit": function noMoar(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 3;
+				
+				var i = 0;
+				lds.pSource = {
+					getCurrent:function(){return { item:i };},
+					eof: function(){return i>=5;},
+					advance: function(){i++; return i<5;}
+				};
+				assert.strictEqual(lds.advance(), true);
+				assert.strictEqual(lds.advance(), false);
+			}
+
+		},
+
+		"#sourceToJson()": {
+
+			"should create an object with a key $skip and the value equal to the skip": function sourceToJsonTest(){
+				var lds = new SkipDocumentSource();
+				lds.skip = 9;
+				var t = {};
+				lds.sourceToJson(t, false);
+				assert.deepEqual(t, { "$skip": 9 });
+			}
+
+		},
+
+		"#createFromJson()": {
+
+			"should return a new SkipDocumentSource object from an input number": function createTest(){
+				var t = SkipDocumentSource.createFromJson(5);
+				assert.strictEqual(t.constructor, SkipDocumentSource);
+				assert.strictEqual(t.skip, 5);
+			}
+
+		}
+
+
+	}
+
+};
+
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+