فهرست منبع

EAGLESIX-2651: Variables: add missing statics, better sync w/ 2.6.5 code and tests

Kyle P Davis 11 سال پیش
والد
کامیت
b4d4780432
2فایلهای تغییر یافته به همراه337 افزوده شده و 310 حذف شده
  1. 95 75
      lib/pipeline/expressions/Variables.js
  2. 242 235
      test/lib/pipeline/expressions/Variables.js

+ 95 - 75
lib/pipeline/expressions/Variables.js

@@ -1,57 +1,72 @@
 "use strict";
 
 /**
- * Class that stores/tracks variables
+ * The state used as input and working space for Expressions.
  * @class Variables
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
- **/
+ */
 var Variables = module.exports = function Variables(numVars, root){
-	if(numVars) {
-		if(typeof numVars !== 'number') {
-			throw new Error('numVars must be a number');
-		}
-	}
+	if (arguments.length === 0) numVars = 0; // This is only for expressions that use no variables (even ROOT).
+	if (typeof numVars !== "number") throw new Error("numVars must be a Number");
+
 	this._root = root || {};
-	this._rest = numVars ? [] : undefined; //An array of `Value`s
+	this._rest = numVars === 0 ? null : new Array(numVars);
 	this._numVars = numVars;
-}, klass = Variables,
-	base = Object,
-	proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+}, klass = Variables, proto = klass.prototype;
 
 
-klass.ROOT_ID = -1;
+klass.uassertValidNameForUserWrite = function uassertValidNameForUserWrite(varName) {
+	// System variables users allowed to write to (currently just one)
+	if (varName === "CURRENT") {
+		return;
+	}
 
-// PROTOTYPE MEMBERS
+	if (!varName)
+		throw new Error("empty variable names are not allowed; uassert code 16866");
 
-/**
- * Sets the root variable
- * @method setRoot
- * @parameter root {Document} The root variable
- **/
-proto.setRoot = function setRoot(root){
-	if(!(root instanceof Object && root.constructor.name === 'Object')) { //NOTE: Type checking cause c++ does this for you
-		throw new Error('root must be an Object');
-	}
-	this._root = root;
-};
+	var firstCharIsValid = (varName[0] >= "a" &&  varName[0] <= "z") ||
+		(varName[0] & "\x80"); // non-ascii
 
-/**
- * Clears the root variable
- * @method clearRoot
- **/
-proto.clearRoot = function clearRoot(){
-	this._root = {};
+	if (!firstCharIsValid)
+		throw new Error("'" + varName + "' starts with an invalid character for a user variable name; uassert code 16867");
+
+    for (var i = 1, l = varName.length; i < l; i++) {
+		var charIsValid = (varName[i] >= 'a' &&  varName[i] <= 'z') ||
+			(varName[i] >= 'A' &&  varName[i] <= 'Z') ||
+			(varName[i] >= '0' &&  varName[i] <= '9') ||
+			(varName[i] == '_') ||
+			(varName[i] & '\x80'); // non-ascii
+
+		if (!charIsValid)
+			throw new Error("'" + varName + "' contains an invalid character " +
+				"for a variable name: '" + varName[i] + "'; uassert code 16868");
+    }
 };
 
-/**
- * Gets the root variable
- * @method getRoot
- * @return {Document} the root variable
- **/
-proto.getRoot = function getRoot(){
-	return this._root;
+klass.uassertValidNameForUserRead = function uassertValidNameForUserRead(varName) {
+	if (!varName)
+		throw new Error("empty variable names are not allowed; uassert code 16869");
+
+	var firstCharIsValid = (varName[0] >= "a" &&  varName[0] <= "z") ||
+		(varName[0] >= "A" &&  varName[0] <= "Z") ||
+		(varName[0] & "\x80"); // non-ascii
+
+	if (!firstCharIsValid)
+		throw new Error("'" + varName + "' starts with an invalid character for a variable name; uassert code 16870");
+
+	for (var i = 1, l = varName.length; i < l; i++) {
+		var charIsValid = (varName[i] >= "a" &&  varName[i] <= "z") ||
+			(varName[i] >= "A" &&  varName[i] <= "Z") ||
+			(varName[i] >= "0" &&  varName[i] <= "9") ||
+			(varName[i] == "_") ||
+			(varName[i] & "\x80"); // non-ascii
+
+		if (!charIsValid)
+			throw new Error("'" + varName + "' contains an invalid character " +
+				"for a variable name: '" + varName[i] + "'; uassert code 16871");
+	}
 };
 
 /**
@@ -59,20 +74,11 @@ proto.getRoot = function getRoot(){
  * @method setValue
  * @param id {Number} The index where the value is stored in the _rest Array
  * @param value {Value} The value to store
- **/
+ */
 proto.setValue = function setValue(id, value) {
-	//NOTE: Some added type enforcement cause c++ does this for you
-	if(typeof id !== 'number') {
-		throw new Error('id must be a Number');
-	}
-
-	if(id === klass.ROOT_ID) {
-		throw new Error("mError 17199: can't use Variables#setValue to set ROOT");
-	}
-	if(id >= this._numVars) { // a > comparator would be off-by-one; i.e. if we have 5 vars, the max id would be 4
-		throw new Error("You have more variables than _numVars");
-	}
-
+	if (typeof id !== "number") throw new Error("id must be a Number");
+	if (id === klass.ROOT_ID) throw new Error("can't use Variables#setValue to set ROOT; massert code 17199");
+	if (id >= this._numVars) throw new Error("Assertion error");
 	this._rest[id] = value;
 };
 
@@ -81,46 +87,60 @@ proto.setValue = function setValue(id, value) {
  * @method getValue
  * @param id {Number} The index where the value was stored
  * @return {Value} The value
- **/
+ */
 proto.getValue = function getValue(id) {
-	//NOTE: Some added type enforcement cause c++ does this for you
-	if(typeof id !== 'number') {
-		throw new Error('id must be a Number');
-	}
-
-	if(id === klass.ROOT_ID) {
+	if (typeof id !== "number") throw new Error("id must be a Number");
+	if (id === klass.ROOT_ID)
 		return this._root;
-	}
-	if(id >= this._numVars) { // a > comparator would be off-by-one; i.e. if we have 5 vars, the max id would be 4
-		throw new Error("Cannot get value; id was greater than _numVars");
-	}
-
+	if (id >= this._numVars) throw new Error("Assertion error");
 	return this._rest[id];
 };
 
-
 /**
  * Get the value for id if it's a document
  * @method getDocument
  * @param id {Number} The index where the document was stored
  * @return {Object} The document
- **/
+ */
 proto.getDocument = function getDocument(id) {
-	//NOTE: Some added type enforcement cause c++ does this for you
-	if(typeof id !== 'number') {
-		throw new Error('id must be a Number');
-	}
+	if (typeof id !== "number") throw new Error("id must be a Number");
 
-	if(id === klass.ROOT_ID) {
+	if (id === klass.ROOT_ID)
 		return this._root;
-	}
-	if(id >= this._numVars) { // a > comparator would be off-by-one; i.e. if we have 5 vars, the max id would be 4
-		throw new Error("Cannot get value; id was greater than _numVars");
-	}
 
+	if (id >= this._numVars) throw new Error("Assertion error");
 	var value = this._rest[id];
-	if(typeof value === 'object' && value.constructor.name === 'Object') {
+	if (value instanceof Object && value.constructor === Object)
 		return value;
-	}
+
 	return {};
 };
+
+klass.ROOT_ID = -1;
+
+/**
+ * Use this instead of setValue for setting ROOT
+ * @method setRoot
+ * @parameter root {Document} The root variable
+ */
+proto.setRoot = function setRoot(root){
+	if (!(root instanceof Object && root.constructor === Object)) throw new Error("Assertion failure");
+	this._root = root;
+};
+
+/**
+ * Clears the root variable
+ * @method clearRoot
+ */
+proto.clearRoot = function clearRoot(){
+	this._root = {};
+};
+
+/**
+ * Gets the root variable
+ * @method getRoot
+ * @return {Document} the root variable
+ */
+proto.getRoot = function getRoot(){
+	return this._root;
+};

+ 242 - 235
test/lib/pipeline/expressions/Variables.js

@@ -2,251 +2,258 @@
 var assert = require("assert"),
 	Variables = require("../../../../lib/pipeline/expressions/Variables");
 
+// Mocha one-liner to make these tests self-hosted
+if (!module.parent)return(require.cache[__filename] = null, (new (require("mocha"))({ui: "exports", reporter: "spec", grep: process.env.TEST_GREP})).addFile(__filename).run(process.exit));
 
-module.exports = {
+exports.Variables = {
 
-	"Variables": {
+	"constructor": {
 
-		"constructor": {
+		"should be able to construct empty variables": function() {
+			new Variables();
+		},
+
+		"should be able to give number of variables": function() {
+			new Variables(5);
+		},
 
-			"Should be able to construct empty variables": function canConstructEmpty() {
+		"should throw if not given a number": function() {
+			assert.throws(function() {
+				new Variables('hi');
+			});
+			assert.throws(function() {
+				new Variables({});
+			});
+			assert.throws(function() {
+				new Variables([]);
+			});
+			assert.throws(function() {
+				new Variables(new Date());
+			});
+		},
+
+		"setValue throws if no args given": function() {
+			assert.throws(function() {
 				var variables = new Variables();
-			},
+				variables.setValue(1, 'hi');
+			});
+		},
 
-			"Should be able to give number of variables": function giveNumber() {
-				var variables = new Variables(5);
-			},
-
-			"Should throw if not given a number": function throwsOnInvalid() {
-				assert.throws(function() {
-					var variables = new Variables('hi');
-				});
-				assert.throws(function() {
-					var variables = new Variables({});
-				});
-				assert.throws(function() {
-					var variables = new Variables([]);
-				});
-				assert.throws(function() {
-					var variables = new Variables(new Date());
-				});
-			},
-
-			"setValue throws if no args given": function setValueThrows() {
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue(1, 'hi');
-				});
-				
-			}
-
-		},
-
-		"#setRoot": {
-			"should set the _root variable to the passed value": function setsRoot() {
-				var variables = new Variables(),
-					root = {'hi':'hi'};
-				variables.setRoot(root);
-				assert.equal(root, variables._root);
-			},
-
-			"must be an object": function mustBeObject() {
-				var variables = new Variables(),
-					root = 'hi';
-				assert.throws(function() {
-					variables.setRoot(root);
-				});
-			}
-		},
-
-		"#clearRoot": {
-			"should set the _root variable to empty obj": function setsRootToEmpty() {
-				var variables = new Variables(),
-					root = {'hi':'hi'};
-				variables.setRoot(root);
-				variables.clearRoot();
-				assert.deepEqual({}, variables._root);
-			}
+	},
+
+	"#setRoot": {
+
+		"should set the _root variable to the passed value": function() {
+			var variables = new Variables(),
+				root = {'hi':'hi'};
+			variables.setRoot(root);
+			assert.equal(root, variables._root);
 		},
 
-		"#getRoot": {
-			"should return the _root variable": function returnsRoot() {
-				var variables = new Variables(),
-					root = {'hi':'hi'};
+		"must be an object": function mustBeObject() {
+			var variables = new Variables(),
+				root = 'hi';
+			assert.throws(function() {
 				variables.setRoot(root);
-				assert.equal(root, variables.getRoot());
-			}
-		},
-
-		"#setValue": {
-			"id must be number": function idMustBeNumber() {
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue('hi', 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue(null, 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue(new Date(), 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue([], 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.setValue({}, 5);
-				});
-				assert.doesNotThrow(function() {
-					var variables = new Variables(5);
-					variables.setValue(1, 5);
-				});
-			},
-
-			"cannot use root id": function cannotUseRootId() {
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.setValue(Variables.ROOT_ID, 'hi');
-				});
-			},
-
-			"cannot use id larger than initial size": function idSizeIsCorrect() {
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.setValue(5, 'hi'); //off by one check
-				});
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.setValue(6, 'hi');
-				});
-			},
-
-			"sets the value": function setsTheValue() {
+			});
+		},
+
+	},
+
+	"#clearRoot": {
+
+		"should set the _root variable to empty obj": function() {
+			var variables = new Variables(),
+				root = {'hi':'hi'};
+			variables.setRoot(root);
+			variables.clearRoot();
+			assert.deepEqual({}, variables._root);
+		},
+
+	},
+
+	"#getRoot": {
+
+		"should return the _root variable": function() {
+			var variables = new Variables(),
+				root = {'hi':'hi'};
+			variables.setRoot(root);
+			assert.equal(root, variables.getRoot());
+		},
+
+	},
+
+	"#setValue": {
+
+		"id must be number": function() {
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.setValue('hi', 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.setValue(null, 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.setValue(new Date(), 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.setValue([], 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.setValue({}, 5);
+			});
+			assert.doesNotThrow(function() {
 				var variables = new Variables(5);
-				variables.setValue(1, 'hi'); //off by one check
-				assert.equal(variables._rest[1], 'hi');
-			}
-		},
-
-		"#getValue": {
-			"id must be number": function idMustBeNumber() {
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getValue('hi', 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getValue(null, 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getValue(new Date(), 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getValue([], 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getValue({}, 5);
-				});
-				assert.doesNotThrow(function() {
-					var variables = new Variables(5);
-					variables.getValue(1, 5);
-				});
-			},
-
-			"returns root when given root id": function returnsRoot() {
-				var variables = new Variables(5),
-					root = {hi:'hi'};
-				variables.setRoot(root);
-				variables.getValue(Variables.ROOT_ID, root);
-			},
-
-			"cannot use id larger than initial size": function idSizeIsCorrect() {
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.getValue(5, 'hi'); //off by one check
-				});
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.getValue(6, 'hi');
-				});
-			},
-
-			"gets the value": function getsTheValue() {
+				variables.setValue(1, 5);
+			});
+		},
+
+		"cannot use root id": function() {
+			assert.throws(function() {
 				var variables = new Variables(5);
-				variables.setValue(1, 'hi');
-				assert.equal(variables.getValue(1), 'hi');
-			}
-		},
-
-		"#getDocument": {
-			"id must be number": function idMustBeNumber() {
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getDocument('hi', 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getDocument(null, 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getDocument(new Date(), 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getDocument([], 5);
-				});
-				assert.throws(function() {
-					var variables = new Variables();
-					variables.getDocument({}, 5);
-				});
-				assert.doesNotThrow(function() {
-					var variables = new Variables(5);
-					variables.getDocument(1, 5);
-				});
-			},
-
-			"returns root when given root id": function returnsRoot() {
-				var variables = new Variables(5),
-					root = {hi:'hi'};
-				variables.setRoot(root);
-				variables.getDocument(Variables.ROOT_ID, root);
-			},
-
-			"cannot use id larger than initial size": function idSizeIsCorrect() {
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.getDocument(5, 'hi'); //off by one check
-				});
-				assert.throws(function() {
-					var variables = new Variables(5);
-					variables.getDocument(6, 'hi');
-				});
-			},
-
-			"gets the value": function getsTheDocument() {
-				var variables = new Variables(5),
-					value = {hi:'hi'};
-				variables.setValue(1, value);
-				assert.equal(variables.getDocument(1), value);
-			},
-
-			"only returns documents": function returnsOnlyDocs() {
-				var variables = new Variables(5),
-					value = 'hi';
-				variables.setValue(1, value);
-				assert.deepEqual(variables.getDocument(1), {});
-			}
-		}
-
-	}
+				variables.setValue(Variables.ROOT_ID, 'hi');
+			});
+		},
 
-};
+		"cannot use id larger than initial size": function() {
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.setValue(5, 'hi'); //off by one check
+			});
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.setValue(6, 'hi');
+			});
+		},
+
+		"sets the value": function() {
+			var variables = new Variables(5);
+			variables.setValue(1, 'hi'); //off by one check
+			assert.equal(variables._rest[1], 'hi');
+		},
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+	},
+
+	"#getValue": {
+
+		"id must be number": function() {
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getValue('hi', 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getValue(null, 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getValue(new Date(), 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getValue([], 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getValue({}, 5);
+			});
+			assert.doesNotThrow(function() {
+				var variables = new Variables(5);
+				variables.getValue(1, 5);
+			});
+		},
+
+		"returns root when given root id": function() {
+			var variables = new Variables(5),
+				root = {hi:'hi'};
+			variables.setRoot(root);
+			variables.getValue(Variables.ROOT_ID, root);
+		},
+
+		"cannot use id larger than initial size": function() {
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.getValue(5, 'hi'); //off by one check
+			});
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.getValue(6, 'hi');
+			});
+		},
+
+		"gets the value": function() {
+			var variables = new Variables(5);
+			variables.setValue(1, 'hi');
+			assert.equal(variables.getValue(1), 'hi');
+		},
+
+	},
+
+	"#getDocument": {
+
+		"id must be number": function() {
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getDocument('hi', 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getDocument(null, 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getDocument(new Date(), 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getDocument([], 5);
+			});
+			assert.throws(function() {
+				var variables = new Variables();
+				variables.getDocument({}, 5);
+			});
+			assert.doesNotThrow(function() {
+				var variables = new Variables(5);
+				variables.getDocument(1, 5);
+			});
+		},
+
+		"returns root when given root id": function() {
+			var variables = new Variables(5),
+				root = {hi:'hi'};
+			variables.setRoot(root);
+			variables.getDocument(Variables.ROOT_ID, root);
+		},
+
+		"cannot use id larger than initial size": function() {
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.getDocument(5, 'hi'); //off by one check
+			});
+			assert.throws(function() {
+				var variables = new Variables(5);
+				variables.getDocument(6, 'hi');
+			});
+		},
+
+		"gets the value": function() {
+			var variables = new Variables(5),
+				value = {hi:'hi'};
+			variables.setValue(1, value);
+			assert.equal(variables.getDocument(1), value);
+		},
+
+		"only returns documents": function() {
+			var variables = new Variables(5),
+				value = 'hi';
+			variables.setValue(1, value);
+			assert.deepEqual(variables.getDocument(1), {});
+		},
+
+	},
+
+};