Browse Source

Merge pull request #116 from RiveraGroup/feature/mongo_2.6.5_documentSource_Project

Feature/mongo 2.6.5 document source project
Chris Sexton 11 years ago
parent
commit
64dcef64a3

+ 39 - 44
lib/pipeline/documentSources/ProjectDocumentSource.js

@@ -10,11 +10,12 @@ var DocumentSource = require('./DocumentSource');
  * @constructor
  * @param [ctx] {ExpressionContext}
  **/
-var ProjectDocumentSource = module.exports = function ProjectDocumentSource(ctx){
-	if (arguments.length > 1) throw new Error("up to one arg expected");
+var ProjectDocumentSource = module.exports = function ProjectDocumentSource(ctx, exprObj){
+	if (arguments.length > 2) throw new Error("up to two args expected");
 	base.call(this, ctx);
-	this.OE = new ObjectExpression();
+	this.OE = new ObjectExpression(exprObj);
 	this._raw = undefined;
+	this._variables = undefined;
 }, klass = ProjectDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 // DEPENDENCIES
@@ -45,9 +46,9 @@ proto.getNext = function getNext(callback) {
 		if (err)
 			return callback(null, err);
 
-		if (input === DocumentSource.EOF) {
+		if (input === null) {
 			out = input;
-			return callback(null, DocumentSource.EOF);
+			return callback(null, null);
 		}
 
 		/* create the result document */
@@ -69,11 +70,11 @@ proto.getNext = function getNext(callback) {
 };
 
 /**
- * Returns the object that was used to construct the ProjectDocumentSource
- * @return {object} the object that was used to construct the ProjectDocumentSource
+ * Optimizes the internal ObjectExpression
+ * @return
  **/
-proto.getRaw = function getRaw() {
-	return this._raw;
+proto.optimize = function optimize() {
+	this.OE = this.OE.optimize();
 };
 
 proto.serialize = function serialize(explain) {
@@ -82,38 +83,13 @@ proto.serialize = function serialize(explain) {
 	return out;
 };
 
-/**
- * Optimizes the internal ObjectExpression
- * @return
- **/
-proto.optimize = function optimize() {
-	this.OE.optimize();
-};
-
-proto.toJSON = function toJSON(){
-	var obj = {};
-	this.sourceToJson(obj);
-	return obj;
-};
-
-/**
- * Places a $project key inside the builder object with value of this.OE
- * @method sourceToJson
- * @param {builder} An object (was ported from BsonBuilder)
- * @return
- **/
-proto.sourceToJson = function sourceToJson(builder, explain) {
-	var insides = this.OE.toJSON(true);
-	builder[this.getSourceName()] = insides;
-};
-
 /**
  * Builds a new ProjectDocumentSource from an object
  * @method createFromJson
  * @return {ProjectDocmentSource} a ProjectDocumentSource instance
  **/
-klass.createFromJson = function(jsonElement, expCtx) {
-	if (!(jsonElement instanceof Object) || jsonElement.constructor !== Object) throw new Error('Error 15969. Specification must be an object but was ' + typeof jsonElement);
+klass.createFromJson = function(elem, expCtx) {
+	if (!(elem instanceof Object) || elem.constructor !== Object) throw new Error('Error 15969. Specification must be an object but was ' + typeof elem);
 
 	var objectContext = new Expression.ObjectCtx({
 		isDocumentOk: true,
@@ -121,17 +97,22 @@ klass.createFromJson = function(jsonElement, expCtx) {
 		isInclusionOk: true
 	});
 
-	var project = new ProjectDocumentSource(expCtx),
-		idGenerator = new VariablesIdGenerator(),
-		vps = new VariablesParseState(idGenerator);
+	var idGenerator = new VariablesIdGenerator(),
+		vps = new VariablesParseState(idGenerator),
+		parsed = Expression.parseObject(elem, objectContext, vps),
+		exprObj = parsed;
 
-	project._raw = jsonElement;
-	var parsed = Expression.parseObject(jsonElement, objectContext, vps);
-	var exprObj = parsed;
 	if (!exprObj instanceof ObjectExpression) throw new Error("16402, parseObject() returned wrong type of Expression");
-	if (!exprObj.getFieldCount()) throw new Error("16403, $projection requires at least one output field");
-	project.OE = exprObj;
+	//if (!exprObj.getFieldCount() ) throw new Error("uassert 16403: $project requires at least one output field");
+
+	var project = new ProjectDocumentSource(expCtx, exprObj);
 	project._variables = new Variables(idGenerator.getIdCount());
+
+	var projectObj = elem
+	project.OE = exprObj;
+
+	project._raw = elem;
+
 	return project;
 };
 
@@ -145,3 +126,17 @@ proto.getDependencies = function getDependencies(deps) {
 	this.OE.addDependencies(deps, path);
 	return base.GetDepsReturn.EXHAUSTIVE;
 };
+
+/**
+ * Returns the object that was used to construct the ProjectDocumentSource
+ * @return {object} the object that was used to construct the ProjectDocumentSource
+ **/
+proto.getRaw = function getRaw() {
+	return this._raw;
+};
+
+proto.toJSON = function toJSON(){
+	var obj = {};
+	this.sourceToJson(obj);
+	return obj;
+};

+ 1 - 1
lib/pipeline/expressions/Expression.js

@@ -88,7 +88,7 @@ klass.parseObject = function parseObject(obj, ctx, vps) {
 		OPERATOR = 2,
 		kind = UNKNOWN;
 
-	if (obj === undefined || obj === null || (obj instanceof Object && Object.keys(obj).length === 0)) return new ObjectExpression();
+	if (obj === undefined || obj === null || (obj instanceof Object && Object.keys(obj).length === 0)) return new ObjectExpression({});
 	var fieldNames = Object.keys(obj);
 	for (var fieldCount = 0, n = fieldNames.length; fieldCount < n; ++fieldCount) {
 		var fieldName = fieldNames[fieldCount];

+ 208 - 222
test/lib/pipeline/documentSources/ProjectDocumentSource.js

@@ -1,10 +1,13 @@
 "use strict";
 var assert = require("assert"),
 	async = require("async"),
+	DepsTracker = require("../../../../lib/pipeline/DepsTracker"),
 	DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
 	ProjectDocumentSource = require("../../../../lib/pipeline/documentSources/ProjectDocumentSource"),
 	CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
-	Cursor = require("../../../../lib/Cursor");
+	ArrayRunner = require("../../../../lib/query/ArrayRunner"),
+	TestBase = require("./TestBase"),
+	And = require("../../../../lib/pipeline/expressions/AndExpression");
 
 
 /**
@@ -12,8 +15,7 @@ var assert = require("assert"),
  *   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();
 	assert.deepEqual(pdsRep, rep);
 };
 
@@ -28,260 +30,244 @@ var createProject = function createProject(projection) {
 			"$project": projection
 		},
 		specElement = projection,
-		project = ProjectDocumentSource.createFromJson(specElement);
-	checkJsonRepresentation(project, spec);
-	return project;
+		_project = ProjectDocumentSource.createFromJson(specElement);
+	checkJsonRepresentation(_project, spec);
+	return _project;
 };
 
 //TESTS
 module.exports = {
 
-	"ProjectDocumentSource": {
+	"constructor()": {
 
-		"constructor()": {
+		"should not throw Error when constructing without args": function testConstructor() {
+			assert.doesNotThrow(function() {
+				new ProjectDocumentSource();
+			});
+		},
 
-			"should not throw Error when constructing without args": function testConstructor() {
-				assert.doesNotThrow(function() {
-					new ProjectDocumentSource();
-				});
-			}
+		"should throw Error when constructing with more than 1 arg": function testConstructor() {
+			assert.throws(function() {
+				new ProjectDocumentSource("a", "b", "c");
+			});
+		}
 
-		},
+	},
 
-		"#getSourceName()": {
+	"#getSourceName()": {
 
-			"should return the correct source name; $project": function testSourceName() {
-				var pds = new ProjectDocumentSource();
-				assert.strictEqual(pds.getSourceName(), "$project");
-			}
+		"should return the correct source name; $project": function testSourceName() {
+			var pds = new ProjectDocumentSource();
+			assert.strictEqual(pds.getSourceName(), "$project");
+		}
 
+	},
+
+	"#getNext()": {
+
+		"should return EOF": function testEOF(next) {
+			var pds = createProject({});
+			pds.setSource({
+				getNext: function getNext(cb) {
+					return cb(null, null);
+				}
+			});
+			pds.getNext(function(err, doc) {
+				assert.equal(null, doc);
+				next();
+			});
 		},
 
-		"#getNext()": {
-
-			"should return EOF": function testEOF(next) {
-				var pds = createProject();
-				pds.setSource({
-					getNext: function getNext(cb) {
-						return cb(null, DocumentSource.EOF);
-					}
-				});
-				pds.getNext(function(err, doc) {
-					assert.equal(DocumentSource.EOF, doc);
-					next();
-				});
-			},
-
-			"iterator state accessors consistently report the source is exhausted": function assertExhausted() {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{}];
-				cwc._cursor = new Cursor( input );
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject();
-				pds.setSource(cds);
-				pds.getNext(function(err, actual) {
-					pds.getNext(function(err, actual1) {
-						assert.equal(DocumentSource.EOF, actual1);
-						pds.getNext(function(err, actual2) {
-							assert.equal(DocumentSource.EOF, actual2);
-							pds.getNext(function(err, actual3) {
-								assert.equal(DocumentSource.EOF, actual3);
-							});
+		"iterator state accessors consistently report the source is exhausted": function assertExhausted() {
+			var input = [{}];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject();
+			pds.setSource(cds);
+			pds.getNext(function(err, actual) {
+				pds.getNext(function(err, actual1) {
+					assert.equal(null, actual1);
+					pds.getNext(function(err, actual2) {
+						assert.equal(null, actual2);
+						pds.getNext(function(err, actual3) {
+							assert.equal(null, actual3);
 						});
 					});
 				});
-			},
-
-			"callback is required": function requireCallback() {
-				var pds = createProject();
-				assert.throws(pds.getNext.bind(pds));
-			},
-
-			"should not return EOF when a document is still in cursor": function testNotEOFTrueIfDocPresent() {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{_id: 0, a: 1}, {_id: 1, a: 2}];
-					cwc._cursor = new Cursor( input );
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject();
-				pds.setSource(cds);
-				pds.getNext(function(err,actual) {
-					// first go round
-					assert.notEqual(actual, DocumentSource.EOF);
-				});
-			},
+			});
+		},
 
-			"can retrieve second document from source": function testAdvanceFirst() {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{_id: 0, a: 1}, {_id: 1, a: 2}];
-				cwc._cursor = new Cursor( input );
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject();
-				pds.setSource(cds);
+		"callback is required": function requireCallback() {
+			var pds = createProject();
+			assert.throws(pds.getNext.bind(pds));
+		},
 
-				pds.getNext(function(err,val) {
-					// eh, ignored
-					pds.getNext(function(err,val) {
-						assert.equal(2, val.a);
-					});
-				});
-			},
-
-			"should get the first document out of a cursor": function getCurrentCalledFirst() {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{_id: 0, a: 1}];
-				cwc._cursor = new Cursor( input );
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject();
-				pds.setSource(cds);
-				pds.getNext(function(err, actual) {
-					assert.equal(1, actual.a);
-				});
-			},
-
-			"The a and c.d fields are included but the b field is not": function testFullProject1(next) {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{
-					_id: 0,
-					a: 1,
-					b: 1,
-					c: {
-						d: 1
-					}
-				}];
-				cwc._cursor = new Cursor(input);
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject({
-						a: true,
-						c: {
-							d: true
-						}
-					}),
-					expected = {a:1, c:{ d: 1 }};
-				pds.setSource(cds);
+		"should not return EOF when a document is still in cursor": function testNotEOFTrueIfDocPresent() {
+			var input = [{_id: 0, a: 1}, {_id: 1, a: 2}];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject();
+			pds.setSource(cds);
+			pds.getNext(function(err,actual) {
+				// first go round
+				assert.notEqual(actual, null);
+			});
+		},
+
+		"can retrieve second document from source": function testAdvanceFirst() {
+			var input = [{_id: 0, a: 1}, {_id: 1, a: 2}];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject();
+			pds.setSource(cds);
 
+			pds.getNext(function(err,val) {
+				// eh, ignored
 				pds.getNext(function(err,val) {
-					assert.deepEqual(expected, val);
-					next();
+					assert.equal(2, val.a);
 				});
-			},
-
-			"Two documents": function testTwoDocumentsProject(next) {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = [{
-					a: 1,
-					b: 2
-				}, {
-					a: 3,
-					b: 4
-				}],
-				expected = [
-					{a:1},
-					{a:3},
-					DocumentSource.EOF
-				];
-				cwc._cursor = new Cursor(input);
-				var cds = new CursorDocumentSource(cwc);
-				var pds = createProject({
+			});
+		},
+
+		"should get the first document out of a cursor": function getCurrentCalledFirst() {
+			var input = [{_id: 0, a: 1}];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject();
+			pds.setSource(cds);
+			pds.getNext(function(err, actual) {
+				assert.equal(1, actual.a);
+			});
+		},
+
+		"The a and c.d fields are included but the b field is not": function testFullProject1(next) {
+			var input = [{
+				_id:0,
+				a: 1,
+				b: 1,
+				c: {
+					d: 1
+				}
+			}];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject({
 					a: true,
 					c: {
 						d: true
 					}
-				});
-				pds.setSource(cds);
-
-				async.series([
-						pds.getNext.bind(pds),
-						pds.getNext.bind(pds),
-						pds.getNext.bind(pds),
-					],
-					function(err,res) {
-						assert.deepEqual(expected, res);
-						next();
-					}
-				);
-			}
+				}),
+				expected = {_id: 0, a:1, c:{ d: 1 }};
+			pds.setSource(cds);
+
+			pds.getNext(function(err,val) {
+				assert.deepEqual(expected, val);
+				next();
+			});
 		},
 
-		"#optimize()": {
+		"Two documents": function testTwoDocumentsProject(next) {
+			var input = [{
+				a: 1,
+				b: 2
+			}, {
+				a: 3,
+				b: 4
+			}],
+			expected = [
+				{a:1},
+				{a:3},
+				null
+			];
+			var cds = new CursorDocumentSource(null, new ArrayRunner(input), null);
+			var pds = createProject({
+				a: true,
+				c: {
+					d: true
+				}
+			});
+			pds.setSource(cds);
+
+			async.series([
+					pds.getNext.bind(pds),
+					pds.getNext.bind(pds),
+					pds.getNext.bind(pds),
+				],
+				function(err,res) {
+					assert.deepEqual(expected, res);
+					next();
+				}
+			);
+		}
+	},
 
-			"Optimize the projection": function optimizeProject() {
-				var pds = createProject({
-					a: {
-						$and: [true]
-					}
-				});
-				pds.optimize();
-				checkJsonRepresentation(pds, {
-					$project: {
-						a: {
-							$const: true
-						}
-					}
-				});
-			}
+	"#optimize()": {
 
-		},
+		"Optimize the projection": function optimizeProject() {
+			var pds = createProject({
+				a: {
+					$and: [{$const:true}]
+				}
+			});
 
-		"#createFromJson()": {
+			pds.optimize();
+			checkJsonRepresentation(pds, {$project:{a:{$const:true}}});
+		}
 
-			"should error if called with non-object": function testNonObjectPassed() {
-				//String as arg
-				assert.throws(function() {
-					var pds = createProject("not an object");
-				});
-				//Date as arg
-				assert.throws(function() {
-					var pds = createProject(new Date());
-				});
-				//Array as arg
-				assert.throws(function() {
-					var pds = createProject([]);
-				});
-				//Empty args
-				assert.throws(function() {
-					var pds = ProjectDocumentSource.createFromJson();
-				});
-				//Top level operator
-				assert.throws(function() {
-					var pds = createProject({
-						$add: []
-					});
+	},
+
+	"#createFromJson()": {
+
+		"should error if called with non-object": function testNonObjectPassed() {
+			//String as arg
+			assert.throws(function() {
+				var pds = createProject("not an object");
+			});
+			//Date as arg
+			assert.throws(function() {
+				var pds = createProject(new Date());
+			});
+			//Array as arg
+			assert.throws(function() {
+				var pds = createProject([]);
+			});
+			//Empty args
+			assert.throws(function() {
+				var pds = ProjectDocumentSource.createFromJson();
+			});
+			//Top level operator
+			assert.throws(function() {
+				var pds = createProject({
+					$add: []
 				});
-				//Invalid spec
-				assert.throws(function() {
-					var pds = createProject({
-						a: {
-							$invalidOperator: 1
-						}
-					});
+			});
+			//Invalid spec
+			assert.throws(function() {
+				var pds = createProject({
+					a: {
+						$invalidOperator: 1
+					}
 				});
+			});
 
-			}
-
-		},
-
-		"#getDependencies()": {
-
-			"should properly detect dependencies in project": function testGetDependencies() {
-				var cwc = new CursorDocumentSource.CursorWithContext();
-				var input = {
-					a: true,
-					x: '$b',
-					y: {
-						$and: ['$c', '$d']
-					}
-				};
-				var pds = createProject(input);
-				var dependencies = {};
-				assert.equal(DocumentSource.GetDepsReturn.EXHAUSTIVE, pds.getDependencies(dependencies));
-				assert.equal(5, Object.keys(dependencies).length);
-				assert.ok(dependencies._id);
-				assert.ok(dependencies.a);
-				assert.ok(dependencies.b);
-				assert.ok(dependencies.c);
-				assert.ok(dependencies.d);
-			}
+		}
 
+	},
+
+	"#getDependencies()": {
+
+		"should properly detect dependencies in project": function testGetDependencies() {
+			var input = {
+				a: true,
+				x: '$b',
+				y: {
+					$and: ['$c', '$d']
+				}
+			};
+			var pds = createProject(input);
+			var dependencies = new DepsTracker();
+			assert.equal(DocumentSource.GetDepsReturn.EXHAUSTIVE, pds.getDependencies(dependencies));
+			assert.equal(5, Object.keys(dependencies.fields).length);
+			assert.ok(dependencies.fields._id);
+			assert.ok(dependencies.fields.a);
+			assert.ok(dependencies.fields.b);
+			assert.ok(dependencies.fields.c);
+			assert.ok(dependencies.fields.d);
 		}
 
 	}

+ 47 - 0
test/lib/pipeline/documentSources/TestBase.js

@@ -0,0 +1,47 @@
+var TestBase = (function() {
+	var klass = function TestBase(overrides) {
+			//NOTE: DEVIATION FROM MONGO: using this base class to make things easier to initialize
+			for (var key in overrides){
+				this[key] = overrides[key];
+			}
+		},
+		proto = klass.prototype;
+	proto.createSource = function() {
+		//TODO: Fix this once we know proper API
+		this._source = CursorDocumentSource.create();
+	};
+	proto.source = function() {
+		return this._source;
+	};
+	proto.createProject = function(projection) {
+		projection = projection || {a:true};
+		var spec = {$project:projection};
+		this._project = ProjectDocumentSource(spec /*,ctx()*/);
+		this.checkJsonRepresentation(spec);
+		this._project.setSource(this.source());
+	};
+	proto.project = function() {
+		return this._project;
+	};
+	proto.assertExhausted = function() {
+		var self = this;
+		self._project.getNext(function(err, input1) {
+			assert.strictEqual(input, DocumentSource.EOF);
+			self._project.getNext(function(err, input2) {
+				assert.strictEqual(input2, DocumentSource.EOF);
+				self._project.getNext(function(err, input3) {
+					assert.strictEqual(input3, DocumentSource.EOF);
+				});
+			});
+		});
+	};
+	proto.checkJsonRepresentation = function() {
+		var arr = [];
+		this._project.serializeToArray(arr);
+		var generatedSpec = arr[0];
+		assert.deepEqual(generatedSpec, spec);
+	};
+	return klass;
+})();
+
+module.exports = TestBase;