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

Merge pull request #144 from RiveraGroup/feature/mongo_2.6.5_jshint_documentSources

Feature/mongo 2.6.5 jshint document sources
Chris Sexton 11 лет назад
Родитель
Сommit
b905475bef

+ 13 - 12
lib/pipeline/documentSources/CursorDocumentSource.js

@@ -1,10 +1,9 @@
 "use strict";
 
-var async = require('async'),
-	Value = require('../Value'),
-	Runner = require('../../query/Runner'),
-	DocumentSource = require('./DocumentSource'),
-	LimitDocumentSource = require('./LimitDocumentSource');
+var async = require("async"),
+	Runner = require("../../query/Runner"),
+	DocumentSource = require("./DocumentSource"),
+	LimitDocumentSource = require("./LimitDocumentSource");
 
 /**
  * Constructs and returns Documents from the BSONObj objects produced by a supplied Runner.
@@ -27,7 +26,7 @@ var CursorDocumentSource = module.exports = CursorDocumentSource = function Curs
 	this._runner = runner;
 
 	this._firstRun = true;
-}, klass = CursorDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = CursorDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.MaxDocumentsToReturnToClientAtOnce = 150; //DEVIATION: we are using documents instead of bytes
 
@@ -70,7 +69,8 @@ proto.dispose = function dispose() {
 /**
  * Get the source's name.
  * @method	getSourceName
- * @returns	{String}	the string name of the source as a constant string; this is static, and there's no need to worry about adopting it
+ * @returns	{String}	the string name of the source as a constant string;
+ * this is static, and there's no need to worry about adopting it
  **/
 proto.getSourceName = function getSourceName() {
 	return "$cursor";
@@ -83,7 +83,7 @@ proto.getSourceName = function getSourceName() {
  **/
 proto.getNext = function getNext(callback) {
 	if (this.expCtx && this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt()){
-		return callback(new Error('Interrupted'));
+		return callback(new Error("Interrupted"));
 	}
 
 	var self = this;
@@ -106,7 +106,8 @@ proto.getNext = function getNext(callback) {
  *
  * @method	coalesce
  * @param	{DocumentSource}	nextSource	the next source in the document processing chain.
- * @returns	{Boolean}	whether or not the attempt to coalesce was successful or not; if the attempt was not successful, nothing has been changed
+ * @returns	{Boolean}	whether or not the attempt to coalesce was successful or not;
+ * if the attempt was not successful, nothing has been changed
  **/
 proto.coalesce = function coalesce(nextSource) {
 	// Note: Currently we assume the $limit is logically after any $sort or
@@ -178,7 +179,7 @@ proto.setProjection = function setProjection(projection, deps) {
  * @param callback  {Function}        a `mungedb-aggregate`-specific extension to the API to half-way support reading from async sources
  **/
 proto.setSource = function setSource(theSource) {
-	throw new Error('this doesnt take a source');
+	throw new Error("this doesnt take a source");
 };
 
 proto.serialize = function serialize(explain) {
@@ -244,7 +245,7 @@ proto.loadBatch = function loadBatch(callback) {
 						}
 						//this was originally a 'verify' in the mongo code
 						if (self._docsAddedToBatches > self._limit.getLimit()){
-							return next(new Error('documents collected past the end of the limit'));
+							return next(new Error("documents collected past the end of the limit"));
 						}
 					}
 
@@ -263,7 +264,7 @@ proto.loadBatch = function loadBatch(callback) {
 			if (!whileReturn){
 				self._runner.reset();
 			}
-			
+
 			//this is a deviation to ensure that the callstack does not get too large if the Runner does things syncronously
 			if (self._firstRun || !self._currentBatch.length){
 				self._firstRun = false;

+ 6 - 4
lib/pipeline/documentSources/DocumentSource.js

@@ -35,7 +35,7 @@ var DocumentSource = module.exports = function DocumentSource(expCtx){
 	*/
 	this.nRowsOut = 0;
 
-}, klass = DocumentSource, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = DocumentSource, proto = klass.prototype;
 
 /*
 class DocumentSource :
@@ -97,7 +97,8 @@ proto.dispose = function dispose() {
 /**
  * Get the source's name.
  * @method	getSourceName
- * @returns	{String}	the string name of the source as a constant string; this is static, and there's no need to worry about adopting it
+ * @returns	{String}	the string name of the source as a constant string;
+ * this is static, and there's no need to worry about adopting it
  **/
 proto.getSourceName = function getSourceName() {
 	return "[UNKNOWN]";
@@ -132,7 +133,8 @@ proto.setSource = function setSource(theSource) {
  *
  * @method	coalesce
  * @param	{DocumentSource}	nextSource	the next source in the document processing chain.
- * @returns	{Boolean}	whether or not the attempt to coalesce was successful or not; if the attempt was not successful, nothing has been changed
+ * @returns	{Boolean}	whether or not the attempt to coalesce was successful or not;
+ * if the attempt was not successful, nothing has been changed
  **/
 proto.coalesce = function coalesce(nextSource) {
 	return false;
@@ -193,7 +195,7 @@ proto.serializeToArray = function serializeToArray(array, explain) {
  * @param callback.doc {Object} The source's next object or null
  **/
 klass.GET_NEXT_PASS_THROUGH = function GET_NEXT_PASS_THROUGH(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
+	if (!callback) throw new Error(this.getSourceName() + " #getNext() requires callback");
 
 	var out;
 	this.source.getNext(function(err, doc) {

+ 17 - 21
lib/pipeline/documentSources/GeoNearDocumentSource.js

@@ -1,8 +1,7 @@
 "use strict";
 
-var async = require('async'),
-	DocumentSource = require('./DocumentSource'),
-	FieldPath = require('../FieldPath');
+var DocumentSource = require("./DocumentSource"),
+	FieldPath = require("../FieldPath");
 
 /**
  * @class GeoNearDocumentSource
@@ -21,7 +20,7 @@ var GeoNearDocumentSource = module.exports = function GeoNearDocumentSource(ctx)
 	this.spherical = false;
 	this.distanceMultiplier = 1.0;
 	this.uniqueDocs = true;
-}, klass = GeoNearDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = GeoNearDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.geoNearName = "$geoNear";
 
@@ -36,7 +35,7 @@ proto.getSourceName = function() {
 proto.getNext = DocumentSource.GET_NEXT_PASS_THROUGH;
 
 proto.setSource = function(docSource) {
-	throw new Error('code 16602; $geoNear is only allowed as the first pipeline stage');
+	throw new Error("code 16602; $geoNear is only allowed as the first pipeline stage");
 };
 
 proto.isValidInitialSource = function() {
@@ -69,8 +68,8 @@ proto.serialize = function(explain) {
 		result.distanceMultiplier = this.distanceMultiplier;
 
 	if (this.includeLocs) {
-		if (typeof this.includeLocs !== 'string')
-			throw new Error('code 16607; $geoNear requires that \'includeLocs\' option is a String');
+		if (typeof this.includeLocs !== "string")
+			throw new Error("code 16607; $geoNear requires that \"includeLocs\" option is a String");
 		result.includeLocs = this.includeLocs.getPath(false);
 	}
 
@@ -94,42 +93,39 @@ proto.parseOptions = function(options) {
 
 	// near and distanceField are required
 	if (!options.near || !Array.isArray(options.near))
-		throw new Error('code 16605; $geoNear requires a \'near\' option as an Array');
+		throw new Error("code 16605; $geoNear requires a \"near\" option as an Array");
 	this.coordsIsArray = options.near instanceof Array;
 
-	if (typeof options.distanceField !== 'string')
-		throw new Error('code 16606: $geoNear  a \'distanceNear\' option as a String');
+	if (typeof options.distanceField !== "string")
+		throw new Error("code 16606: $geoNear  a \"distanceNear\" option as a String");
 	this.distanceField = new FieldPath(options.distanceField);
 
 	// remaining fields are optional
 
 	// num and limits are synonyms
-	if (typeof options.limit === 'number')
+	if (typeof options.limit === "number")
 		this.limit = options.limit;
-	if (typeof options.num === 'number')
+	if (typeof options.num === "number")
 		this.limit = options.num;
 
-	if (typeof options.maxDistance === 'number')
+	if (typeof options.maxDistance === "number")
 		this.maxDistance = options.maxDistance;
 
 	if (options.query instanceof Object)
 		this.query = options.query;
 
 	if (options.spherical)
-		this.spherical = options.spherical;
+		this.spherical = Boolean(options.spherical);
 
-	if (typeof options.distanceMultiplier === 'number')
+	if (typeof options.distanceMultiplier === "number")
 		this.distanceMultiplier = options.distanceMultiplier;
 
 	if (options.includeLocs) {
-		if (typeof options.includeLocs !== 'string')
-			throw new Error('code 16607; $geoNear requires that \'includeLocs\' option is a String');
+		if (typeof options.includeLocs !== "string")
+			throw new Error("code 16607; $geoNear requires that \"includeLocs\" option is a String");
 		this.includeLocs = new FieldPath(options.includeLocs);
 	}
 
 	if (options.uniqueDocs)
-		this.uniqueDocs;
+		this.uniqueDocs = Boolean(options.uniqueDocs);
 };
-
-
-

+ 38 - 27
lib/pipeline/documentSources/GroupDocumentSource.js

@@ -1,7 +1,6 @@
 "use strict";
 var DocumentSource = require("./DocumentSource"),
-	Accumulators = require("../accumulators/"),
-	Document = require("../Document"),
+	accumulators = require("../accumulators/"),
 	Expression = require("../expressions/Expression"),
 	ConstantExpression = require("../expressions/ConstantExpression"),
 	FieldPathExpression = require("../expressions/FieldPathExpression"),
@@ -40,20 +39,20 @@ var GroupDocumentSource = module.exports = function GroupDocumentSource(expCtx)
 	this.expressions = [];
 	this.idExpressions = [];
 	this.currentGroupsKeysIndex = 0;
-}, klass = GroupDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = GroupDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.isSplittableDocumentSource = true;
 
 // TODO: Do we need this?
 klass.groupOps = {
-	"$addToSet": Accumulators.AddToSetAccumulator.create,
-	"$avg": Accumulators.AvgAccumulator.create,
-	"$first": Accumulators.FirstAccumulator.create,
-	"$last": Accumulators.LastAccumulator.create,
-	"$max": Accumulators.MinMaxAccumulator.createMax, // $min and $max have special constructors because they share base features
-	"$min": Accumulators.MinMaxAccumulator.createMin,
-	"$push": Accumulators.PushAccumulator.create,
-	"$sum": Accumulators.SumAccumulator.create,
+	"$addToSet": accumulators.AddToSetAccumulator.create,
+	"$avg": accumulators.AvgAccumulator.create,
+	"$first": accumulators.FirstAccumulator.create,
+	"$last": accumulators.LastAccumulator.create,
+	"$max": accumulators.MinMaxAccumulator.createMax, // $min and $max have special constructors because they share base features
+	"$min": accumulators.MinMaxAccumulator.createMin,
+	"$push": accumulators.PushAccumulator.create,
+	"$sum": accumulators.SumAccumulator.create,
 };
 
 klass.groupName = "$group";
@@ -86,7 +85,7 @@ proto.getSourceName = function getSourceName() {
  * @return {Object}
  **/
 proto.getNext = function getNext(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback.');
+	if (!callback) throw new Error(this.getSourceName() + " #getNext() requires callback.");
 	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false)
 		return callback(new Error("Interrupted"));
 
@@ -108,13 +107,13 @@ proto.getNext = function getNext(callback) {
 				if(self.currentGroupsKeysIndex === self.groupsKeys.length) {
 					return next(null, null);
 				}
-				
+
 				var out;
 				try {
 					var id = self.originalGroupsKeys[self.currentGroupsKeysIndex],
 						stringifiedId = self.groupsKeys[self.currentGroupsKeysIndex],
 						accumulators = self.groups[stringifiedId];
-						
+
 					out = self.makeDocument(id, accumulators, self.expCtx.inShard);
 
 					if(++self.currentGroupsKeysIndex === self.groupsKeys.length) {
@@ -138,7 +137,7 @@ proto.getNext = function getNext(callback) {
  * @method dispose
  **/
 proto.dispose = function dispose() {
-	//NOTE: Skipped 'freeing' our resources; at best we could remove some references to things, but our parent will probably forget us anyways!
+	//NOTE: Skipped 'freeing' our resources; at best we could remove some refs
 
 	// make us look done
 	this.currentGroupsKeysIndex = this.groupsKeys.length;
@@ -214,7 +213,7 @@ proto.serialize = function serialize(explain) {
  * @method	createFromJson
  * @param elem {Object} The group specification object; the right hand side of the $group
  **/
-klass.createFromJson = function createFromJson(elem, expCtx) {
+klass.createFromJson = function createFromJson(elem, expCtx) { //jshint maxcomplexity:17
 	if (!(elem instanceof Object && elem.constructor === Object)) throw new Error("a group's fields must be specified in an object");
 
 	var group = GroupDocumentSource.create(expCtx),
@@ -234,16 +233,22 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
 				group.parseIdExpression(groupField, vps);
 				idSet = true;
 
-			} else if (groupFieldName === '$doingMerge' && groupField) {
+			} else if (groupFieldName === "$doingMerge" && groupField) {
 				throw new Error("17030 $doingMerge should be true if present");
 			} else {
 				/*
 					Treat as a projection field with the additional ability to
 					add aggregation operators.
 				*/
-				if (groupFieldName.indexOf(".") !== -1) throw new Error("16414 the group aggregate field name '" + groupFieldName + "' cannot contain '.'");
-				if (groupFieldName[0] === "$") throw new Error("15950 the group aggregate field name '" + groupFieldName + "' cannot be an operator name");
-				if (group._getTypeStr(groupFieldName) === "Object") throw new Error("15951 the group aggregate field '" + groupFieldName + "' must be defined as an expression inside an object");
+				if (groupFieldName.indexOf(".") !== -1)
+					throw new Error("the group aggregate field name '" + groupFieldName +
+						"' cannot be used because $group's field names cannot contain '.'; uassert code 16414");
+				if (groupFieldName[0] === "$")
+					throw new Error("the group aggregate field name '" +
+						groupFieldName + "' cannot be an operator name; uassert 15950");
+				if (group._getTypeStr(groupFieldName) === "Object")
+					throw new Error("the group aggregate field '" + groupFieldName +
+						"' must be defined as an expression inside an object; uassert 15951");
 
 				var subElementCount = 0;
 				for (var subElementName in groupField) {
@@ -267,7 +272,9 @@ klass.createFromJson = function createFromJson(elem, expCtx) {
 						++subElementCount;
 					}
 				}
-				if (subElementCount !== 1) throw new Error("15954 the computed aggregate '" + groupFieldName + "' must specify exactly one operator");
+				if (subElementCount !== 1)
+					throw new Error("the computed aggregate '" +
+						groupFieldName + "' must specify exactly one operator; uassert code 15954");
 			}
 		}
 	}
@@ -331,7 +338,7 @@ proto.populate = function populate(callback) {
 					//NOTE: Skipped memory usage stuff for case when group already existed
 
 					if(numAccumulators !== group.length) {
-						throw new Error('Group must have one of each accumulator');
+						throw new Error("Group must have one of each accumulator");
 					}
 
 					//NOTE: passing the input to each accumulator
@@ -482,11 +489,11 @@ proto.expandId = function expandId(val) {
  */
 proto.parseIdExpression = function parseIdExpression(groupField, vps) {
 	var self = this;
-	if (self._getTypeStr(groupField) === 'Object' && Object.keys(groupField).length !== 0) {
+	if (self._getTypeStr(groupField) === "Object" && Object.keys(groupField).length !== 0) {
 		// {_id: {}} is treated as grouping on a constant, not an expression
 
 		var idKeyObj = groupField;
-		if (Object.keys(idKeyObj)[0][0] == '$') {
+		if (Object.keys(idKeyObj)[0][0] === "$") {
 			var objCtx = new Expression.ObjectCtx({});
 			self.idExpressions.push(Expression.parseObject(idKeyObj, objCtx, vps));
 		} else {
@@ -497,7 +504,7 @@ proto.parseIdExpression = function parseIdExpression(groupField, vps) {
 				self.idExpressions.push(Expression.parseOperand(field[key], vps));
 			});
 		}
-	} else if (self._getTypeStr(groupField) === 'string' && groupField[0] === '$') {
+	} else if (self._getTypeStr(groupField) === "string" && groupField[0] === "$") {
 		self.idExpressions.push(FieldPathExpression.parse(groupField, vps));
 	} else {
 		self.idExpressions.push(ConstantExpression.create(groupField));
@@ -513,7 +520,7 @@ proto.parseIdExpression = function parseIdExpression(groupField, vps) {
  **/
 proto._getTypeStr = function _getTypeStr(obj) {
 	var typeofStr = typeof obj,
-		typeStr = (typeofStr == "object" && obj !== null) ? obj.constructor.name : typeofStr;
+		typeStr = (typeofStr === "object" && obj !== null) ? obj.constructor.name : typeofStr;
 	return typeStr;
 };
 
@@ -529,8 +536,12 @@ proto.getMergeSource = function getMergeSource() {
 		vps = new VariablesParseState(idGenerator);
 
 	merger.idExpressions.push(FieldPathExpression.parse("$$ROOT._id", vps));
+
 	for (var i = 0; i < self.fieldNames.length; i++) {
-		merger.addAccumulator(self.fieldNames[i], self.accumulatorFactories[i], FieldPathExpression.create("$$ROOT." + self.fieldNames[i], vps));
+		merger.addAccumulator(
+			self.fieldNames[i], self.accumulatorFactories[i],
+			FieldPathExpression.create("$$ROOT." + self.fieldNames[i], vps)
+		);
 	}
 
 	return merger;

+ 3 - 3
lib/pipeline/documentSources/LimitDocumentSource.js

@@ -1,6 +1,6 @@
 "use strict";
 
-var DocumentSource = require('./DocumentSource');
+var DocumentSource = require("./DocumentSource");
 
 /**
  * A document source limiter
@@ -15,7 +15,7 @@ var LimitDocumentSource = module.exports = function LimitDocumentSource(ctx, lim
 	base.call(this, ctx);
 	this.limit = limit;
 	this.count = 0;
-}, klass = LimitDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = LimitDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.limitName = "$limit";
 
@@ -50,7 +50,7 @@ proto.coalesce = function coalesce(nextSource) {
 * @return {bool} indicating end of document reached
 */
 proto.getNext = function getNext(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires 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"));

+ 21 - 23
lib/pipeline/documentSources/MatchDocumentSource.js

@@ -1,7 +1,6 @@
 "use strict";
 var async = require("async"),
-	matcher = require("../matcher/Matcher2.js"),
-	DocumentSource = require("./DocumentSource");
+	matcher = require("../matcher/Matcher2.js");
 
 /**
  * A match document source built off of DocumentSource
@@ -29,7 +28,7 @@ var MatchDocumentSource = module.exports = function MatchDocumentSource(query, c
 	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 = MatchDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.matchName = "$match";
 
@@ -38,10 +37,10 @@ proto.getSourceName = function getSourceName(){
 };
 
 proto.getNext = function getNext(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
+	if (!callback) throw new Error(this.getSourceName() + " #getNext() requires callback");
 
 	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false) {
-		return callback(new Error('Interrupted'));
+		return callback(new Error("Interrupted"));
 	}
 
 	var self = this,
@@ -112,7 +111,8 @@ klass.uassertNoDisallowedClauses = function uassertNoDisallowedClauses(query) {
 };
 
 klass.createFromJson = function createFromJson(jsonElement, ctx) {
-	if (!(jsonElement instanceof Object) || jsonElement.constructor !== Object) throw new Error("code 15959 ; the match filter must be an expression in an object");
+	if (!(jsonElement instanceof Object) || jsonElement.constructor !== Object)
+		throw new Error("code 15959 ; the match filter must be an expression in an object");
 	klass.uassertNoDisallowedClauses(jsonElement);
 	var matcher = new MatchDocumentSource(jsonElement, ctx);
 	return matcher;
@@ -123,7 +123,7 @@ proto.isTextQuery = function isTextQuery() {
 };
 
 klass.isTextQuery = function isTextQuery(query) {
-    for (var key in query) {
+    for (var key in query) { //jshint ignore:line
         var fieldName = key;
         if (fieldName === "$text") return true;
         if (query[key] instanceof Object && query[key].constructor === Object && this.isTextQuery(query[key])) {
@@ -152,8 +152,6 @@ proto.getQuery = function getQuery() {
  * that have had their age field removed.
  */
 proto.redactSafePortion = function redactSafePortion() {
-	var self = this;
-
 	// This block contains the functions that make up the implementation of
 	// DocumentSourceMatch::redactSafePortion(). They will only be called after
 	// the Match expression has been successfully parsed so they can assume that
@@ -164,7 +162,7 @@ proto.redactSafePortion = function redactSafePortion() {
 	};
 
 	var isFieldnameRedactSafe = function isFieldnameRedactSafe(field) {
-		var dotPos = field.indexOf('.');
+		var dotPos = field.indexOf(".");
 		if (dotPos === -1)
 			return !isAllDigits(field);
 
@@ -177,27 +175,27 @@ proto.redactSafePortion = function redactSafePortion() {
 	// Returns the redact-safe portion of an "inner" match expression. This is the layer like
 	// {$gt: 5} which does not include the field name. Returns an empty document if none of the
 	// expression can safely be promoted in front of a $redact.
-	var redactSavePortionDollarOps = function redactSafePortionDollarOps(expr) {
+	var redactSavePortionDollarOps = function redactSafePortionDollarOps(expr) { //jshint maxcomplexity:23
 		var output = {},
-			elem,i,j,k;
+			elem, i, j;
 
 		var keys = Object.keys(expr);
 		for (i = 0; i < keys.length; i++) {
 			var field = keys[i],
 				value = expr[field];
 
-			if (field[0] !== '$')
+			if (field[0] !== "$")
 				continue;
 
 			// Ripped the case apart and did not implement this painful thing:
 			// https://github.com/mongodb/mongo/blob/r2.5.4/src/mongo/db/jsobj.cpp#L286
 			// Somebody should be taken to task for that work of art.
-			if (field === '$type' || field === '$regex' || field === '$options' || field === '$mod') {
+			if (field === "$type" || field === "$regex" || field === "$options" || field === "$mod") {
 				output[field] = value;
-			} else if (field === '$lte' || field === '$gte' || field === '$lt' || field === '$gt') {
+			} else if (field === "$lte" || field === "$gte" || field === "$lt" || field === "$gt") {
 				if (isTypeRedactSafeInComparison(field))
 					output[field] = value;
-			} else if (field === '$in') {
+			} else if (field === "$in") {
 				// TODO: value/elem/field/etc may be mixed up and wrong here
 				var allOk = true;
 				for (j = 0; j < Object.keys(value).length; j++) {
@@ -211,7 +209,7 @@ proto.redactSafePortion = function redactSafePortion() {
 					output[field] = value;
 				}
 				break;
-			} else if (field === '$all') {
+			} else if (field === "$all") {
 				// TODO: value/elem/field/etc may be mixed up and wrong here
 				var matches = [];
 				for (j = 0; j < value.length; j++) {
@@ -222,11 +220,11 @@ proto.redactSafePortion = function redactSafePortion() {
 				if (matches.length)
 					output[field] = matches;
 
-			} else if (field === '$elemMatch') {
+			} else if (field === "$elemMatch") {
 				var subIn = value,
 					subOut;
 
-				if (subIn[0] === '$')
+				if (subIn[0] === "$")
 					subOut = redactSafePortionDollarOps(subIn);
 				else
 					subOut = redactSafePortionTopLevel(subIn);
@@ -254,7 +252,7 @@ proto.redactSafePortion = function redactSafePortion() {
 	// Returns the redact-safe portion of an "outer" match expression. This is the layer like
 	// {fieldName: {...}} which does include the field name. Returns an empty document if none of
 	// the expression can safely be promoted in front of a $redact.
-	var redactSafePortionTopLevel = function(topQuery) {
+	var redactSafePortionTopLevel = function(topQuery) { //jshint maxcomplexity:18
 		var output = {},
 			okClauses = [],
 			keys = topQuery ? Object.keys(topQuery) : [],
@@ -264,8 +262,8 @@ proto.redactSafePortion = function redactSafePortion() {
 			var field = keys[i],
 				value = topQuery[field];
 
-			if (field.length && field[0] === '$') {
-				if (field === '$or') {
+			if (field.length && field[0] === "$") {
+				if (field === "$or") {
 					okClauses = [];
 					for (j = 0; j < Object.keys(value).length; j++) {
 						elm = value[Object.keys(value)[j]];
@@ -282,7 +280,7 @@ proto.redactSafePortion = function redactSafePortion() {
 					if (okClauses && okClauses.length) {
 						output.$or = okClauses;
 					}
-				} else if (field === '$and') {
+				} else if (field === "$and") {
 					okClauses = [];
 					for (j = 0; j < Object.keys(value).length; j++) {
 						elm = value[Object.keys(value)[j]];

+ 4 - 4
lib/pipeline/documentSources/OutDocumentSource.js

@@ -1,6 +1,6 @@
 "use strict";
 
-var DocumentSource = require('./DocumentSource');
+var DocumentSource = require("./DocumentSource");
 
 /**
  * @class OutDocumentSource
@@ -16,7 +16,7 @@ var OutDocumentSource = module.exports = function OutDocumentSource(outputNs, ct
 	this._done = false;
 	this._outputNs = outputNs;
 	this._collectionName = "";
-}, klass = OutDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = OutDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.outName = "$out";
 
@@ -38,8 +38,8 @@ proto.getOutputNs = function() {
 };
 
 klass.createFromJson = function(jsonElement, ctx) {
-	if (typeof jsonElement !== 'string')
-		throw new Error('code 16990; $out only supports a string argument, not ' + typeof jsonElement);
+	if (typeof jsonElement !== "string")
+		throw new Error("code 16990; $out only supports a string argument, not " + typeof jsonElement);
 
 	var out = new OutDocumentSource(null, ctx); // TODO: outputNs
 	out._collectionName = jsonElement;

+ 11 - 14
lib/pipeline/documentSources/ProjectDocumentSource.js

@@ -1,6 +1,10 @@
 "use strict";
 
-var DocumentSource = require('./DocumentSource');
+var Expression = require("../expressions/Expression"),
+	ObjectExpression = require("../expressions/ObjectExpression"),
+	Variables = require("../expressions/Variables"),
+	VariablesIdGenerator = require("../expressions/VariablesIdGenerator"),
+	VariablesParseState = require("../expressions/VariablesParseState");
 
 /**
  * A base class for filter document sources
@@ -16,15 +20,7 @@ var ProjectDocumentSource = module.exports = function ProjectDocumentSource(ctx,
 	this.OE = ObjectExpression.create();
 	this._raw = undefined;
 	this._variables = undefined;
-}, klass = ProjectDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-// DEPENDENCIES
-var Expression = require('../expressions/Expression'),
-	ObjectExpression = require('../expressions/ObjectExpression'),
-	Value = require('../Value'),
-	Variables = require('../expressions/Variables'),
-	VariablesIdGenerator = require('../expressions/VariablesIdGenerator'),
-	VariablesParseState = require('../expressions/VariablesParseState');
+}, klass = ProjectDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.projectName = "$project";
 
@@ -37,7 +33,7 @@ proto.getSourceName = function getSourceName() {
 };
 
 proto.getNext = function getNext(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires callback');
+	if (!callback) throw new Error(this.getSourceName() + " #getNext() requires callback");
 
 	var self = this,
 		out;
@@ -93,7 +89,8 @@ proto.serialize = function serialize(explain) {
  * @return {ProjectDocmentSource} a ProjectDocumentSource instance
  **/
 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);
+	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,
@@ -113,9 +110,9 @@ klass.createFromJson = function(elem, expCtx) {
 	project._variables = new Variables(idGenerator.getIdCount());
 
 	var projectObj = elem;
-	project.OE = exprObj;
+	project._raw = projectObj;
 
-	project._raw = elem;
+	project.OE = exprObj;
 
 	return project;
 };

+ 7 - 6
lib/pipeline/documentSources/RedactDocumentSource.js

@@ -1,7 +1,6 @@
 "use strict";
 
 var async = require("async"),
-	DocumentSource = require("./DocumentSource"),
 	Expression = require("../expressions/Expression"),
 	Variables = require("../expressions/Variables"),
 	VariablesIdGenerator = require("../expressions/VariablesIdGenerator"),
@@ -21,16 +20,16 @@ var RedactDocumentSource = module.exports = function RedactDocumentSource(ctx, e
 	this._expression = expression;
 	this._variables = new Variables();
 	this._currentId = null;
-}, klass = RedactDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = RedactDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 klass.redactName = "$redact";
 proto.getSourceName = function getSourceName(){
 	return klass.redactName;
 };
 
-var DESCEND_VAL = 'descend',
-	PRUNE_VAL = 'prune',
-	KEEP_VAL = 'keep';
+var DESCEND_VAL = "descend",
+	PRUNE_VAL = "prune",
+	KEEP_VAL = "keep";
 
 proto.getNext = function getNext(callback) {
 	var self = this,
@@ -120,7 +119,9 @@ proto.redactObject = function redactObject() {
 
 		return out;
 	} else {
-		throw new Error("17053 $redact's expression should not return anything aside from the variables $$KEEP, $$DESCEND, and $$PRUNE, but returned " + expressionResult);
+		throw new Error("$redact's expression should not return anything " +
+			"aside from the variables $$KEEP, $$DESCEND, and " +
+			"$$PRUNE, but returned " + expressionResult + "; uasserted code 17053");
 	}
 };
 

+ 10 - 10
lib/pipeline/documentSources/SkipDocumentSource.js

@@ -1,7 +1,7 @@
 "use strict";
 
-var async = require('async'),
-	DocumentSource = require('./DocumentSource');
+var async = require("async"),
+	DocumentSource = require("./DocumentSource");
 
 /**
  * A document source skipper.
@@ -14,7 +14,7 @@ var async = require('async'),
  **/
 var SkipDocumentSource = module.exports = function SkipDocumentSource(ctx) {
 	if (arguments.length > 1) {
-		throw new Error('Up to one argument expected.');
+		throw new Error("Up to one argument expected.");
 	}
 
 	base.call(this, ctx);
@@ -23,9 +23,9 @@ var SkipDocumentSource = module.exports = function SkipDocumentSource(ctx) {
 	this.count = 0;
 
 	this.needToSkip = true;
-}, klass = SkipDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
+}, klass = SkipDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}}); //jshint ignore:line
 
-klass.skipName = '$skip';
+klass.skipName = "$skip";
 
 /**
  * Return the source name.
@@ -64,11 +64,11 @@ proto.coalesce = function coalesce(nextSource) {
  */
 proto.getNext = function getNext(callback) {
 	if (!callback) {
-		throw new Error(this.getSourceName() + ' #getNext() requires callback.');
+		throw new Error(this.getSourceName() + " #getNext() requires callback.");
 	}
 
 	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false) {
-		return callback(new Error('Interrupted'));
+		return callback(new Error("Interrupted"));
 	}
 
 	var self = this,
@@ -148,8 +148,8 @@ klass.create = function create(expCtx) {
  * @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 15972; the value to skip must be a number');
+	if (typeof jsonElement !== "number") {
+		throw new Error("code 15972; the value to skip must be a number");
 	}
 
 	var nextSkip = new SkipDocumentSource(ctx);
@@ -157,7 +157,7 @@ klass.createFromJson = function createFromJson(jsonElement, ctx) {
 	nextSkip.skip = jsonElement;
 
 	if (nextSkip.skip < 0 || isNaN(nextSkip.skip)) {
-		throw new Error('code 15956; the number to skip cannot be negative');
+		throw new Error("code 15956; the number to skip cannot be negative");
 	}
 
 	return nextSkip;

+ 26 - 21
lib/pipeline/documentSources/SortDocumentSource.js

@@ -2,8 +2,7 @@
 
 var async = require("async"),
 	DocumentSource = require("./DocumentSource"),
-	LimitDocumentSource = require("./LimitDocumentSource"),
-	Document = require('../Document');
+	LimitDocumentSource = require("./LimitDocumentSource");
 
 /**
  * A document source sorter
@@ -31,7 +30,7 @@ var SortDocumentSource = module.exports = function SortDocumentSource(ctx){
 
 	this.vSortKey = [];
 	this.vAscending = [];
-}, klass = SortDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = SortDocumentSource, base = require("./DocumentSource"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
 // DEPENDENCIES
 var FieldPathExpression = require("../expressions/FieldPathExpression"),
@@ -74,7 +73,7 @@ proto.coalesce = function coalesce(nextSource) {
 };
 
 proto.getNext = function getNext(callback) {
-	if (!callback) throw new Error(this.getSourceName() + ' #getNext() requires 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"));
@@ -254,10 +253,11 @@ klass.IteratorFromCursor = (function(){
 })();
 
 proto.populateFromCursors = function populateFromCursors(cursors){
-	for (var i = 0; i < cursors.length; i++) {
-		// TODO Create class
-		//this.iterators.push(boost::make_shared<IteratorFromBsonArray>(this, cursors[i]));
-	}
+	//TODO: umm, probably broken...
+	// for (var i = 0; i < cursors.length; i++) {
+	// 	// TODO Create class
+	// 	//this.iterators.push(boost::make_shared<IteratorFromBsonArray>(this, cursors[i]));
+	// }
 
 	this._output.reset( ); // TODO: MySorter::Iterator::merge(iterators, makeSortOptions(), Comparator(*this))
 
@@ -288,10 +288,11 @@ klass.IteratorFromBsonArray = (function(){
 })();
 
 proto.populateFromBsonArrays = function populateFromBsonArrays(arrays){
-	for (var i = 0; i < arrays.lenth; i++) {
-		// TODO Create class
-		//this.iterators.push(boost::make_shared<IteratorFromBsonArray>(this, arrays[i]));
-	}
+	//TODO: umm, probably broken...
+	// for (var i = 0; i < arrays.lenth; i++) {
+	// 	// TODO Create class
+	// 	//this.iterators.push(boost::make_shared<IteratorFromBsonArray>(this, arrays[i]));
+	// }
 	this._output.reset( ); // TODO: MySorter::Iterator::merge(iterators, makeSortOptions(), Comparator(*this))
 };
 
@@ -304,7 +305,7 @@ proto.populateFromBsonArrays = function populateFromBsonArrays(arrays){
 proto.extractKey = function extractKey(d){
 	var vars = new Variables(0,d);
 
-	if ( this.vSortKey.length == 1)
+	if ( this.vSortKey.length === 1)
 		return this.vSortKey[0].evaluate(vars);
 
 	var keys;
@@ -330,7 +331,7 @@ proto.compare = function compare(lhs,rhs) {
 	*/
 
 	for(var i = 0, n = this.vSortKey.length; i < n; ++i) {
-		var pathExpr = FieldPathExpression.create(this.vSortKey[i].getFieldPath(false).fieldNames.slice(1).join('.'));
+		var pathExpr = FieldPathExpression.create(this.vSortKey[i].getFieldPath(false).fieldNames.slice(1).join("."));
 
 		/* evaluate the sort keys */
 		var left = pathExpr.evaluate(lhs), right = pathExpr.evaluate(rhs);
@@ -366,7 +367,7 @@ proto.serializeSortKey = function serializeSortKey(explain) {
 	var n = this.vSortKey.length;
 	for (var i = 0; i < n; i++) {
 		if ( this.vSortKey[i] instanceof FieldPathExpression ) {
-			var fieldPath = this.vSortKey[i].getFieldPath(false).fieldNames.slice(1).join('.');
+			var fieldPath = this.vSortKey[i].getFieldPath(false).fieldNames.slice(1).join(".");
 			// append a named integer based on the sort order
 			keyObj[fieldPath] = this.vAscending[i] ? 1 : -1;
 		} else {
@@ -405,7 +406,7 @@ klass.create = function create(expCtx, sortOrder, limit) {
 
 	/* check for then iterate over the sort object */
 	var sortKeys = 0;
-	for(var keyField in sortOrder) {
+	for(var keyField in sortOrder) { //jshint ignore:line
 		var fieldName = keyField.fieldName;
 
 		if ( fieldName === "$mergePresorted" ){
@@ -418,23 +419,27 @@ klass.create = function create(expCtx, sortOrder, limit) {
 			throw new Error("code 17312; " + klass.sortName + "the only expression supported by $sort right now is {$meta: 'textScore'}");
 		}
 
-		if (typeof sortOrder[keyField] !== "number") throw new Error("code 15974; " + klass.sortName + "$sort key ordering must be specified using a number or {$meta: 'text'}");
+		if (typeof sortOrder[keyField] !== "number")
+			throw new Error("code 15974; " + klass.sortName + "$sort key ordering must be specified using a number or {$meta: 'text'}");
 
 		// RedBeard0531 can the thanked.
 		var sortDirection = 0;
 		sortDirection = sortOrder[keyField];
-		if ((sortDirection != 1) && (sortDirection !== -1)) throw new Error("code 15975; " + klass.sortName + " $sort key ordering must be 1 (for ascending) or -1 (for descending)");
+		if (sortDirection !== 1 && sortDirection !== -1)
+			throw new Error("code 15975; " + klass.sortName + " $sort key ordering must be 1 (for ascending) or -1 (for descending)");
 
 		nextSort.addKey(keyField, (sortDirection > 0));
 		++sortKeys;
 	}
 
-	if (sortKeys <= 0) throw new Error("code 15976; " + klass.sortName + " must have at least one sort key");
+	if (sortKeys <= 0)
+		throw new Error("code 15976; " + klass.sortName + " must have at least one sort key");
 
 
 	if ( limit > 0) {
-		var coalesced = nextSort.coalesce( create(expCtx, limit));
-		// should always coalesce
+		var coalesced = nextSort.coalesce(LimitDocumentSource.create(expCtx, limit));
+		if (!coalesced) throw new Error("Assertion failure"); // should always coalesce
+		if (nextSort.getLimit() === limit) throw new Error("Assertion failure"); // should always coalesce
 	}
 
 	return nextSort;

+ 16 - 17
lib/pipeline/documentSources/UnwindDocumentSource.js

@@ -1,11 +1,10 @@
 "use strict";
 
-var async = require('async'),
-	DocumentSource = require('./DocumentSource'),
-	Expression = require('../expressions/Expression'),
-	FieldPath = require('../FieldPath'),
-	Value = require('../Value'),
-	Document = require('../Document');
+var async = require("async"),
+	DocumentSource = require("./DocumentSource"),
+	Expression = require("../expressions/Expression"),
+	FieldPath = require("../FieldPath"),
+	Document = require("../Document");
 
 /**
  * A document source unwinder
@@ -17,16 +16,16 @@ var async = require('async'),
  **/
 var UnwindDocumentSource = module.exports = function UnwindDocumentSource(ctx){
 	if (arguments.length > 1) {
-		throw new Error('Up to one argument expected.');
+		throw new Error("Up to one argument expected.");
 	}
 
 	base.call(this, ctx);
 
 	this._unwindPath = null; // Configuration state.
 	this._unwinder = null; // Iteration state.
-}, klass = UnwindDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = UnwindDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
 
-klass.unwindName = '$unwind';
+klass.unwindName = "$unwind";
 
 klass.Unwinder = (function() {
 	/**
@@ -44,7 +43,7 @@ klass.Unwinder = (function() {
 	}, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
 
 	proto.resetDocument = function resetDocument(document) {
-		if (!document) throw new Error('Document is required!');
+		if (!document) throw new Error("Document is required!");
 
 		this._inputArray = [];
 		this._document = document;
@@ -57,7 +56,7 @@ klass.Unwinder = (function() {
 		}
 
 		if (!(pathValue instanceof Array)) {
-			throw new Error(UnwindDocumentSource.unwindName + ':  value at end of field path must be an array; code 15978');
+			throw new Error(UnwindDocumentSource.unwindName + ":  value at end of field path must be an array; code 15978");
 		}
 
 		this._inputArray = pathValue;
@@ -102,11 +101,11 @@ proto.getSourceName = function getSourceName() {
  */
 proto.getNext = function getNext(callback) {
 	if (!callback) {
-		throw new Error(this.getSourceName() + ' #getNext() requires callback.');
+		throw new Error(this.getSourceName() + " #getNext() requires callback.");
 	}
 
 	if (this.expCtx.checkForInterrupt && this.expCtx.checkForInterrupt() === false) {
-		return callback(new Error('Interrupted'));
+		return callback(new Error("Interrupted"));
 	}
 
 	var self = this,
@@ -168,7 +167,7 @@ proto.getNext = function getNext(callback) {
  */
 proto.serialize = function serialize(explain) {
 	if (!this._unwindPath) {
-		throw new Error('unwind path does not exist!');
+		throw new Error("unwind path does not exist!");
 	}
 
 	var doc = {};
@@ -187,7 +186,7 @@ proto.serialize = function serialize(explain) {
  */
 proto.getDependencies = function getDependencies(deps) {
 	if (!this._unwindPath) {
-		throw new Error('unwind path does not exist!');
+		throw new Error("unwind path does not exist!");
 	}
 
 	deps.fields[this._unwindPath.getPath(false)] = 1;
@@ -203,7 +202,7 @@ proto.getDependencies = function getDependencies(deps) {
  */
 proto.unwindPath = function unwindPath(fieldPath) {
 	if (this._unwindPath) {
-		throw new Error(this.getSourceName() + ' can\'t unwind more than one path; code 15979');
+		throw new Error(this.getSourceName() + " can't unwind more than one path; code 15979");
 	}
 
 	// Record the unwind path.
@@ -218,7 +217,7 @@ proto.unwindPath = function unwindPath(fieldPath) {
 **/
 klass.createFromJson = function createFromJson(jsonElement, ctx) {
 	if (jsonElement.constructor !== String) {
-		throw new Error('the ' + klass.unwindName + ' field path must be specified as a string; code 15981');
+		throw new Error("the " + klass.unwindName + " field path must be specified as a string; code 15981");
 	}
 
 	var pathString = Expression.removeFieldPrefix(jsonElement),