Просмотр исходного кода

Refs #5130: Update Unwind to 2.5.4

Chris Sexton 12 лет назад
Родитель
Сommit
ef4505be9e

+ 46 - 80
lib/pipeline/documentSources/UnwindDocumentSource.js

@@ -1,5 +1,7 @@
 "use strict";
 
+var async = require("async");
+
 /**
  * A document source unwinder
  * @class UnwindDocumentSource
@@ -66,6 +68,21 @@ klass.Unwinder = (function(){
 		this._unwindArrayIteratorCurrent = this._unwindArrayIterator.splice(0,1)[0];
 	};
 
+	/**
+	 * getNext
+	 *
+	 * This is just wrapping the old functions because they are somewhat different
+	 * than the original mongo implementation, but should get updated to follow the current API.
+	 **/
+	proto.getNext = function getNext() {
+		if (this.eof())
+			return DocumentSource.EOF;
+
+		var output = this.getCurrent();
+		this.advance();
+		return output;
+	};
+
 	/**
 	 * eof
 	 * @returns	{Boolean}	true if done unwinding the last document passed to resetDocument().
@@ -166,40 +183,6 @@ klass.Unwinder = (function(){
 	return klass;
 })();
 
-/**
- * Lazily construct the _unwinder and initialize the iterator state of this DocumentSource.
- * To be called by all members that depend on the iterator state.
- **/
-proto.lazyInit = function lazyInit(){
-	if (!this._unwinder) {
-		if (!this._unwindPath){
-			throw new Error("unwind path does not exist!");
-		}
-		this._unwinder = new klass.Unwinder(this._unwindPath);
-		if (!this.source.eof()) {
-			// Set up the first source document for unwinding.
-			this._unwinder.resetDocument(this.source.getCurrent());
-		}
-		this.mayAdvanceSource();
-	}
-};
-
-/**
- * If the _unwinder is exhausted and the source may be advanced, advance the source and
- * reset the _unwinder's source document.
- **/
-proto.mayAdvanceSource = function mayAdvanceSource(){
-	while(this._unwinder.eof()) {
-		// The _unwinder is exhausted.
-
-		if (this.source.eof()) return; // The source is exhausted.
-		if (!this.source.advance()) return; // The source is exhausted.
-
-		// Reset the _unwinder with source's next document.
-		this._unwinder.resetDocument(this.source.getCurrent());
-	}
-};
-
 /**
  * Specify the field to unwind.
 **/
@@ -209,6 +192,7 @@ proto.unwindPath = function unwindPath(fieldPath){
 
 	// Record the unwind path.
 	this._unwindPath = new FieldPath(fieldPath);
+	this._unwinder = new klass.Unwinder(this._unwindPath);
 };
 
 klass.unwindName = "$unwind";
@@ -231,55 +215,37 @@ proto.getDependencies = function getDependencies(deps) {
 	return DocumentSource.GetDepsReturn.SEE_NEXT;
 };
 
-
-/**
- * Is the source at EOF?
- * @method	eof
-**/
-proto.eof = function eof() {
-	this.lazyInit();
-	return this._unwinder.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.lazyInit();
-	return this._unwinder.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
-	this.lazyInit();
-	this._unwinder.advance();
-	this.mayAdvanceSource();
-	return !this._unwinder.eof();
+proto.getNext = function getNext(callback) {
+	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
+
+	var self = this,
+		out = this._unwinder.getNext();
+	async.whilst(
+		function () {
+			return out === DocumentSource.EOF;
+		},
+		function (cb) {
+			self.source.getNext(function(err,input) {
+				if (input === DocumentSource.EOF)
+					return cb(input);
+				self._unwinder.resetDocument(input);
+				out = self._unwinder.getNext();
+				cb();
+			});
+		},
+		function (err) {
+			if (err === DocumentSource.EOF)
+				return callback(null, DocumentSource.EOF);
+			return callback(null, out);
+		}
+	);
 };
 
-/**
- * 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) {
+proto.serialize = function serialize(explain) {
 	if (!this._unwindPath) throw new Error("unwind path does not exist!");
-	builder[this.getSourceName()] = this._unwindPath.getPath(true);
+	var doc = {};
+	doc[this.getSourceName()] = this._unwindPath.getPath(true);
+	return doc;
 };
 
 /**

+ 111 - 89
test/lib/pipeline/documentSources/UnwindDocumentSource.js

@@ -1,5 +1,6 @@
 "use strict";
 var assert = require("assert"),
+	async = require("async"),
 	DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
 	UnwindDocumentSource = require("../../../../lib/pipeline/documentSources/UnwindDocumentSource"),
 	CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
@@ -17,8 +18,7 @@ var assertExhausted = function assertExhausted(pds) {
 *   MUST CALL WITH A PDS AS THIS (e.g. checkJsonRepresentation.call(this, rep) where this is a PDS)
 **/
 var checkJsonRepresentation = function checkJsonRepresentation(self, rep) {
-	var pdsRep = {};
-	self.sourceToJson(pdsRep, true);
+	var pdsRep = self.serialize(true);
 	assert.deepEqual(pdsRep, rep);
 };
 
@@ -42,34 +42,36 @@ var addSource = function addSource(unwind, data) {
 	unwind.setSource(cds);
 };
 
-var checkResults = function checkResults(data, expectedResults, path) {
-	//poplateData?
-	//createSource?
+var checkResults = function checkResults(data, expectedResults, path, next) {
+	if (expectedResults instanceof Function)
+		next = expectedResults, expectedResults = null, path = null;
+	if (path instanceof Function)
+		next = path, path = null;
 
 	var unwind = createUnwind(path);
 	addSource(unwind, data || []);
 
 	expectedResults = expectedResults || [];
 
+	expectedResults.push(DocumentSource.EOF);
+
 	//Load the results from the DocumentSourceUnwind
-	var resultSet = [];
-    while (!unwind.eof()) {
-		// If not eof, current is non null.
-		assert.ok(unwind.getCurrent());
-
-		// Get the current result.
-		resultSet.push(unwind.getCurrent());
-
-		// Advance.
-		if (unwind.advance()) {
-			// If advance succeeded, eof() is false.
-			assert.equal(unwind.eof(), false);
+	var docs = [], i = 0;
+	async.doWhilst(
+		function(cb) {
+			unwind.getNext(function(err, val) {
+				docs[i] = val;
+				return cb(err);
+			});
+		},
+		function() {
+			return docs[i++] !== DocumentSource.EOF;
+		},
+		function(err) {
+			assert.deepEqual(expectedResults, docs);
+			next();
 		}
-    }
-	// Verify the DocumentSourceUnwind is exhausted.
-	assertExhausted(unwind);
-
-    assert.deepEqual(resultSet, expectedResults);
+	);
 };
 
 var throwsException = function throwsException(data, path, expectedResults) {
@@ -103,135 +105,153 @@ module.exports = {
 
         },
 
-        "#eof()": {
+        "#getNext()": {
 
-            "should return true if source is empty": function (){
+            "should return EOF if source is empty": function (next){
                 var pds = createUnwind();
                 addSource(pds, []);
-                assert.strictEqual(pds.eof(), true);
+				pds.getNext(function(err,doc) {
+					assert.strictEqual(doc, DocumentSource.EOF);
+					next();
+				});
             },
-            
-            "should return false if source documents exist": function (){
-                var pds = createUnwind();
-                addSource(pds, [{_id:0, a:[1]}]);
-                assert.strictEqual(pds.eof(), false);
-            }
 
-        },
-
-        "#advance()": {
-
-            "should return false if there are no documents in the parent source": function () {
+            "should return document if source documents exist": function (next){
                 var pds = createUnwind();
-                addSource(pds, []);
-                assert.strictEqual(pds.advance(), false);
+                addSource(pds, [{_id:0, a:[1]}]);
+				pds.getNext(function(err,doc) {
+					assert.notStrictEqual(doc, DocumentSource.EOF);
+					next();
+				});
             },
 
-            "should return true if source documents exist and advance the source": function (){
+            "should return document if source documents exist and advance the source": function (next){
                 var pds = createUnwind();
                 addSource(pds, [{_id:0, a:[1,2]}]);
-                assert.strictEqual(pds.advance(), true);
-                assert.strictEqual(pds.getCurrent().a, 2);
-            }
-
-        },
-
-        "#getCurrent()": {
-
-            "should return null if there are no documents in the parent source": function () {
-                var pds = createUnwind();
-                addSource(pds, []);
-                assert.strictEqual(pds.getCurrent(), null);
+				pds.getNext(function(err,doc) {
+					assert.notStrictEqual(doc, DocumentSource.EOF);
+					assert.strictEqual(doc.a, 1);
+					pds.getNext(function(err,doc) {
+						assert.strictEqual(doc.a, 2);
+						next();
+					});
+				});
             },
 
-            "should return unwound documents": function (){
+            "should return unwound documents": function (next){
                 var pds = createUnwind();
                 addSource(pds, [{_id:0, a:[1,2]}]);
-                assert.ok(pds.getCurrent());
-                assert.strictEqual(pds.getCurrent().a, 1);
+
+				var docs = [], i = 0;
+				async.doWhilst(
+					function(cb) {
+						pds.getNext(function(err, val) {
+							docs[i] = val;
+							return cb(err);
+						});
+					},
+					function() {
+						return docs[i++] !== DocumentSource.EOF;
+					},
+					function(err) {
+						assert.deepEqual([{_id:0, a:1},{_id:0, a:2},DocumentSource.EOF], docs);
+						next();
+					}
+				);
             },
-            
-            "A document without the unwind field produces no results.": function(){
-				checkResults([{}]);
+
+            "A document without the unwind field produces no results.": function (next){
+				checkResults([{}],next);
             },
 
-            "A document with a null field produces no results.": function(){
-				checkResults([{a:null}]);
+            "A document with a null field produces no results.": function (next){
+				checkResults([{a:null}],next);
             },
 
-            "A document with an empty array produces no results.": function(){
-				checkResults([{a:[]}]);
+            "A document with an empty array produces no results.": function (next){
+				checkResults([{a:[]}],next);
             },
 
-            "A document with a number field produces a UserException.": function(){
-				throwsException([{a:1}]);
+            "A document with a number field produces a UserException.": function (next){
+				throwsException([{a:1}],next);
+				next();
             },
 
-            "An additional document with a number field produces a UserException.": function(){
-				throwsException([{a:[1]}, {a:1}]);
+            "An additional document with a number field produces a UserException.": function (next){
+				throwsException([{a:[1]}, {a:1}],next);
+				next();
             },
 
-            "A document with a string field produces a UserException.": function(){
-				throwsException([{a:"foo"}]);
+            "A document with a string field produces a UserException.": function (next){
+				throwsException([{a:"foo"}],next);
+				next();
             },
 
-            "A document with an object field produces a UserException.": function(){
-				throwsException([{a:{}}]);
+            "A document with an object field produces a UserException.": function (next){
+				throwsException([{a:{}}],next);
+				next();
             },
 
-            "Unwind an array with one value.": function(){
+            "Unwind an array with one value.": function (next){
 				checkResults(
 					[{_id:0, a:[1]}],
-					[{_id:0,a:1}]
+					[{_id:0,a:1}],
+					next
 				);
             },
 
-            "Unwind an array with two values.": function(){
+            "Unwind an array with two values.": function (next){
 				checkResults(
 					[{_id:0, a:[1, 2]}],
-					[{_id:0,a:1}, {_id:0,a:2}]
+					[{_id:0,a:1}, {_id:0,a:2}],
+					next
 				);
             },
 
-            "Unwind an array with two values, one of which is null.": function(){
+            "Unwind an array with two values, one of which is null.": function (next){
 				checkResults(
 					[{_id:0, a:[1, null]}],
-					[{_id:0,a:1}, {_id:0,a:null}]
+					[{_id:0,a:1}, {_id:0,a:null}],
+					next
 				);
             },
 
-            "Unwind two documents with arrays.": function(){
+            "Unwind two documents with arrays.": function (next){
 				checkResults(
 					[{_id:0, a:[1,2]}, {_id:0, a:[3,4]}],
-					[{_id:0,a:1}, {_id:0,a:2}, {_id:0,a:3}, {_id:0,a:4}]
+					[{_id:0,a:1}, {_id:0,a:2}, {_id:0,a:3}, {_id:0,a:4}],
+					next
 				);
             },
 
-            "Unwind an array in a nested document.": function(){
+            "Unwind an array in a nested document.": function (next){
 				checkResults(
 					[{_id:0,a:{b:[1,2],c:3}}],
 					[{_id:0,a:{b:1,c:3}},{_id:0,a:{b:2,c:3}}],
-					"$a.b"
+					"$a.b",
+					next
 				);
             },
 
-            "A missing array (that cannot be nested below a non object field) produces no results.": function(){
+            "A missing array (that cannot be nested below a non object field) produces no results.": function (next){
 				checkResults(
 					[{_id:0,a:4}],
 					[],
-					"$a.b"
+					"$a.b",
+					next
 				);
             },
 
-            "Unwind an array in a doubly nested document.": function(){
+            "Unwind an array in a doubly nested document.": function (next){
 				checkResults(
 					[{_id:0,a:{b:{d:[1,2],e:4},c:3}}],
 					[{_id:0,a:{b:{d:1,e:4},c:3}},{_id:0,a:{b:{d:2,e:4},c:3}}],
-					"$a.b.d"
+					"$a.b.d",
+					next
 				);
             },
 
-            "Unwind several documents in a row.": function(){
+            "Unwind several documents in a row.": function (next){
 				checkResults(
 					[
 						{_id:0,a:[1,2,3]},
@@ -247,11 +267,12 @@ module.exports = {
 						{_id:3,a:10},
                         {_id:3,a:20},
                         {_id:4,a:30}
-                    ]
+                    ],
+					next
 				);
             },
 
-            "Unwind several more documents in a row.": function(){
+            "Unwind several more documents in a row.": function (next){
 				checkResults(
 					[
 						{_id:0,a:null},
@@ -275,10 +296,11 @@ module.exports = {
                         {_id:6,a:7},
                         {_id:6,a:8},
                         {_id:6,a:9}
-					]
+					],
+					next
 				);
             }
-            
+
         },
 
         "#createFromJson()": {
@@ -319,4 +341,4 @@ module.exports = {
 
 };
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);