Pārlūkot izejas kodu

Merge pull request #29 from RiveraGroup/feature/mongo_2.6.5_expressions_Substr

EAGLESIX-2720 expression Substr ported to mongo 2.6.5
Kyle P Davis 11 gadi atpakaļ
vecāks
revīzija
157a809f2f

+ 23 - 30
lib/pipeline/expressions/SubstrExpression.js

@@ -7,43 +7,36 @@
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
- **/
+ */
 var SubstrExpression = module.exports = function SubstrExpression() {
+	if (arguments.length !== 0) throw new Error(klass.name + ": no args expected");
 	base.call(this);
-}, klass = SubstrExpression,
-	FixedArityExpression = require("./FixedArityExpressionT")(klass, 3),
-	base = FixedArityExpression,
-	proto = klass.prototype = Object.create(base.prototype, {
-		constructor: {
-			value: klass
-		}
-	});
+}, klass = SubstrExpression, base = require("./FixedArityExpressionT")(klass, 3), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-// DEPENDENCIES
 var Value = require("../Value"),
 	Expression = require("./Expression");
 
-// PROTOTYPE MEMBERS
-proto.getOpName = function getOpName() {
-	return "$substr";
-};
-
-/**
- * Takes a string and two numbers. The first number represents the number of bytes in the string to skip, and the second number specifies the number of bytes to return from the string.
- * @method evaluateInternal
- **/
 proto.evaluateInternal = function evaluateInternal(vars) {
-	var val = this.operands[0].evaluateInternal(vars),
-		idx = this.operands[1].evaluateInternal(vars),
-		len = this.operands[2].evaluateInternal(vars),
-		str = Value.coerceToString(val);
-	if (typeof(idx) != "number") throw new Error(this.getOpName() + ": starting index must be a numeric type; code 16034");
-	if (typeof(len) != "number") throw new Error(this.getOpName() + ": length must be a numeric type; code 16035");
-	if (idx >= str.length) return "";
-	//TODO: Need to handle -1
-	len = (len === -1 ? undefined : len);
-	return str.substr(idx, len);
+	var string = this.operands[0].evaluateInternal(vars),
+		pLower = this.operands[1].evaluateInternal(vars),
+		pLength = this.operands[2].evaluateInternal(vars);
+
+	var str = Value.coerceToString(string);
+	if (typeof pLower !== "number") throw new Error(this.getOpName() + ":  starting index must be a numeric type (is type " + Value.getType(pLower) + "); uassert code 16034");
+	if (typeof pLength !== "number") throw new Error(this.getOpName() + ":  length must be a numeric type (is type " + Value.getType(pLength) + "); uassert code 16035");
+
+	var lower = Value.coerceToLong(pLower),
+		length = Value.coerceToLong(pLength);
+	if (lower >= str.length) {
+		// If lower > str.length() then string::substr() will throw out_of_range, so return an
+		// empty string if lower is not a valid string index.
+		return "";
+	}
+	return str.substr(lower, length);
 };
 
-/** Register Expression */
 Expression.registerExpression("$substr", base.parse);
+
+proto.getOpName = function getOpName() {
+	return "$substr";
+};

+ 0 - 161
test/lib/pipeline/expressions/SubstrExpression.js

@@ -1,161 +0,0 @@
-"use strict";
-var assert = require("assert"),
-		SubstrExpression = require("../../../../lib/pipeline/expressions/SubstrExpression"),
-		Expression = require("../../../../lib/pipeline/expressions/Expression");
-
-
-module.exports = {
-
-		"SubstrExpression": {
-
-				"constructor()": {
-
-						"should not throw Error when constructing without args": function testConstructor() {
-								assert.doesNotThrow(function() {
-										new SubstrExpression();
-								});
-						}
-
-				},
-
-				"#getOpName()": {
-
-						"should return the correct op name; $substr": function testOpName() {
-								assert.equal(new SubstrExpression().getOpName(), "$substr");
-						}
-
-				},
-
-				"#evaluateInternal()": {
-
-						"Should fail if no end argument is given": function testMissing3rdArg() {
-								var s = "mystring",
-										start = 0,
-										end = s.length;
-								assert.throws(function() {
-										Expression.parseOperand({
-												$substr: ["$s", "$start"]
-										}).evaluateInternal({
-												s: s,
-												start: start
-										});
-								});
-						},
-
-						"Should return entire string when called with 0 and length": function testWholeString() {
-								var s = "mystring",
-										start = 0,
-										end = s.length;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end
-								}), "mystring");
-						},
-
-						"Should return entire string less the last character when called with 0 and length-1": function testLastCharacter() {
-								var s = "mystring",
-										start = 0,
-										end = s.length;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end - 1
-								}), "mystrin");
-						},
-
-						"Should return empty string when 0 and 0 are given as indexes": function test00Indexes() {
-								var s = "mystring",
-										start = 0,
-										end = 0;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end
-								}), "");
-						},
-
-						"Should first character when 0 and 1 are given as indexes": function testFirstCharacter() {
-								var s = "mystring",
-										start = 0,
-										end = 1;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end
-								}), "m");
-						},
-
-						"Should return empty string when empty string is given": function testEmptyString() {
-								var s = "",
-										start = 0,
-										end = 0;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end
-								}), "");
-						},
-
-						"Should return the entire string if end is -1": function testIndexTooLarge() {
-								var s = "mystring",
-										start = 0,
-										end = -1;
-								assert.strictEqual(Expression.parseOperand({
-										$substr: ["$s", "$start", "$end"]
-								}).evaluateInternal({
-										s: s,
-										start: start,
-										end: end
-								}), "mystring");
-						},
-
-
-						"Should fail if end is before begin": function testUnorderedIndexes() {
-								var s = "mystring",
-										start = s.length,
-										end = 0;
-								assert.throws(function() {
-										Expression.parseOperand({
-												$substr: ["$s", "$start"]
-										}).evaluateInternal({
-												s: s,
-												start: start,
-												end: end
-										});
-								});
-						},
-
-						"Should fail if end is greater than length": function testIndexTooLarge() {
-								var s = "mystring",
-										start = 0,
-										end = s.length + 1;
-								assert.throws(function() {
-										Expression.parseOperand({
-												$substr: ["$s", "$start"]
-										}).evaluateInternal({
-												s: s,
-												start: start,
-												end: end
-										});
-								});
-						},
-
-
-				}
-
-		}
-
-};
-
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);

+ 109 - 0
test/lib/pipeline/expressions/SubstrExpression_test.js

@@ -0,0 +1,109 @@
+"use strict";
+var assert = require("assert"),
+	SubstrExpression = require("../../../../lib/pipeline/expressions/SubstrExpression"),
+	Expression = require("../../../../lib/pipeline/expressions/Expression"),
+	VariablesIdGenerator = require("../../../../lib/pipeline/expressions/VariablesIdGenerator"),
+	VariablesParseState = require("../../../../lib/pipeline/expressions/VariablesParseState"),
+	utils = require("./utils"),
+	constify = utils.constify,
+	expressionToJson = utils.expressionToJson;
+
+// 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));
+
+var TestBase = 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];
+	},
+	ExpectedResultBase = (function() {
+		var klass = function ExpectedResultBase() {
+			base.apply(this, arguments);
+		}, base = TestBase, proto = klass.prototype = Object.create(base.prototype);
+		proto.run = function(){
+			var specElement = this.spec(),
+				idGenerator = new VariablesIdGenerator(),
+				vps = new VariablesParseState(idGenerator),
+				expr = Expression.parseOperand(specElement, vps);
+			assert.deepEqual(constify(specElement), expressionToJson(expr));
+			assert.deepEqual(this.expectedResult, expr.evaluate({}));
+		};
+		proto.spec = function() { return {$substr:[this.str, this.offset, this.length]}; };
+		return klass;
+	})();
+
+exports.SubstrExpression = {
+
+	"constructor()": {
+
+		"should construct instance": function() {
+			assert(new SubstrExpression() instanceof SubstrExpression);
+			assert(new SubstrExpression() instanceof Expression);
+		},
+
+		"should error if given args": function() {
+			assert.throws(function() {
+				new SubstrExpression("bad stuff");
+			});
+		},
+
+	},
+
+	"#getOpName()": {
+
+		"should return the correct op name; $substr": function() {
+			assert.equal(new SubstrExpression().getOpName(), "$substr");
+		},
+
+	},
+
+	"evaluate": {
+
+		"should return full string (if contains null)": function FullNull() {
+			new ExpectedResultBase({
+				str: "a\0b",
+				offset: 0,
+				length: 3,
+				get expectedResult(){ return this.str; },
+			}).run();
+		},
+
+		"should return tail of string (if begin at null)": function BeginAtNull() {
+			new ExpectedResultBase({
+				str: "a\0b",
+				offset: 1,
+				length: 2,
+				expectedResult: "\0b",
+			}).run();
+		},
+
+		"should return head of string (if end at null)": function EndAtNull() {
+			new ExpectedResultBase({
+				str: "a\0b",
+				offset: 0,
+				length: 2,
+				expectedResult: "a\0",
+			}).run();
+		},
+
+		"should return tail of string (if head has null) ": function DropBeginningNull() {
+			new ExpectedResultBase({
+				str: "\0b",
+				offset: 1,
+				length: 1,
+				expectedResult: "b",
+			}).run();
+		},
+
+		"should return head of string (if tail has null)": function DropEndingNull() {
+			new ExpectedResultBase({
+				str: "a\0",
+				offset: 0,
+				length: 1,
+				expectedResult: "a",
+			}).run();
+		},
+
+	},
+
+};