Browse Source

MapExpression added (mongo has no tests for this)

Brennan Chesley 11 years ago
parent
commit
ea209698fe

+ 14 - 15
lib/pipeline/expressions/ConcatExpression.js

@@ -1,6 +1,8 @@
 "use strict";
 
-/** 
+var Expression = require("./Expression");
+
+/**
  * Creates an expression that concatenates a set of string operands.
  * @class ConcatExpression
  * @namespace mungedb-aggregate.pipeline.expressions
@@ -21,24 +23,21 @@ proto.getOpName = function getOpName(){
 	return "$concat";
 };
 
-proto.getFactory = function getFactory(){
-	return klass;	// using the ctor rather than a separate .create() method
-};
-
 /**
  * Concats a string of values together.
  * @method evaluate
  **/
 proto.evaluateInternal = function evaluateInternal(vars) {
-	var result = "";
-	for (var i = 0, n = this.operands.length; i < n; ++i) {
-		var val = this.operands[i].evaluateInternal(vars);
-		if (val === null) return null; // if any operand is null, return null for all
-		if (typeof(val) != "string") throw new Error("$concat only supports strings, not " + typeof(val) + "; code 16702");
-		result = result + Value.coerceToString(val);
-	}
-	return result;
+    var n = this.operands.length;
+
+    return this.operands.map(function(x) {
+        var y = x.evaluateInternal(vars);
+        if(typeof(y) !== "string") {
+            throw new Error("$concat only supports strings - 16702");
+        }
+        return y;
+    }).join("");
 };
 
-/** Register Expression */
-Expression.registerExpression("$concat", klass.parse(ConcatExpression));
+
+Expression.registerExpression("$concat", base.parse(ConcatExpression));

+ 25 - 21
lib/pipeline/expressions/Expression.js

@@ -13,22 +13,19 @@
  * @module mungedb-aggregate
  * @constructor
  **/
+
+
 var Expression = module.exports = function Expression() {
 		if (arguments.length !== 0) throw new Error("zero args expected");
 }, klass = Expression,
-		base = Object,
-		proto = klass.prototype = Object.create(base.prototype, {
-				constructor: {
-						value: klass
-				}
-		});
+    base = Object,
+    proto = klass.prototype = Object.create(base.prototype, {
+	constructor: {
+            value: klass
+	}
+    });
+
 
-// DEPENDENCIES
-var Document = require("../Document");
-var ObjectExpression = require("./ObjectExpression");
-var FieldPathExpression;
-var ConstantExpression;
-var kinds;
 
 function fn(){
 	return;
@@ -207,15 +204,15 @@ klass.parseExpression = function parseExpression(exprKey, exprValue, vps) {
  * @returns the parsed operand, as an Expression
  **/
 klass.parseOperand = function parseOperand(exprElement, vps) {
-		var t = typeof(exprElement);
-		if (t === "string" && exprElement[0] == "$") { //if we got here, this is a field path expression
-				return new FieldPathExpression.parse(exprElement, vps);
-		} else
-		if (t === "object" && exprElement && exprElement.constructor === Object)
-				return Expression.parseObject(exprElement, new ObjectCtx({
-						isDocumentOk: true
-				}), vps);
-		else return ConstantExpression.parse(exprElement, vps);
+                var t = typeof(exprElement);
+                if (t === "string" && exprElement[0] == "$") { //if we got here, this is a field path expression
+                    return new FieldPathExpression.parse(exprElement, vps);
+                } else
+                if (t === "object" && exprElement && exprElement.constructor === Object)
+                                return Expression.parseObject(exprElement, new ObjectCtx({
+                                                isDocumentOk: true
+                                }), vps);
+                else return ConstantExpression.parse(exprElement, vps);
 };
 
 /**
@@ -286,3 +283,10 @@ proto.getIsSimple = function getIsSimple() {
 proto.toMatcherBson = function toMatcherBson() {
 		throw new Error("WAS NOT IMPLEMENTED BY INHERITOR!"); //verify(false && "Expression::toMatcherBson()");
 };
+
+
+// DEPENDENCIES
+var Document = require("../Document");
+var ObjectExpression = require("./ObjectExpression");
+var FieldPathExpression = require("./FieldPathExpression");
+var ConstantExpression = require("./ConstantExpression");

+ 2 - 2
lib/pipeline/expressions/FieldPathExpression.js

@@ -60,8 +60,8 @@ proto.evaluateInternal = function evaluateInternal(vars){
  * @param vps variablesParseState
  * @returns a new FieldPathExpression
  **/
-proto.parse = function parse(raw, vps) {
-    if(raw[0] === "$") {
+klass.parse = function parse(raw, vps) {
+    if(raw[0] !== "$") {
         throw new Error("FieldPath: '" + raw + "' doesn't start with a $");
     }
     if(raw.length === 1) {

+ 2 - 12
lib/pipeline/expressions/LetExpression.js

@@ -46,23 +46,13 @@ proto.parse = function parse(expr, vpsIn){
         var id = vpsSub.defineVariable(varName);
 
         vars[id] = {};
-        vars[id][varName] = this.parseOperand(varsElem, vpsIn);
+        vars[id][varName] = Expression.parseOperand(varsElem, vpsIn);
     }
 
-    var subExpression = this.parseOperand(inElem, vpsSub);
+    var subExpression = Expression.parseOperand(inElem, vpsSub);
     return new LetExpression(vars, subExpression);
 };
 
-
-/**
- * evaluateInternal(), but return a Document instead of a Value-wrapped Document.
- * @method evaluateDocument
- * @param currentDoc the input Document
- * @returns the result document
- **/
-proto.evaluateInternal = function evaluateInternal(vars) { //TODO: collapse with #evaluateDocument()?
-};
-
 proto.optimize = function optimize() {
     if(this._variables.empty()) {
         return this._subExpression.optimize();

+ 107 - 0
lib/pipeline/expressions/MapExpression.js

@@ -0,0 +1,107 @@
+"use strict";
+
+var MapExpression = module.exports = function MapExpression(varName, varId, input, each){
+    if (arguments.length !== 4) throw new Error("Four args expected");
+    this._varName = varName;
+    this._varId = varId;
+    this._input = input;
+    this._each = each;
+}, klass = MapExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+
+// DEPENDENCIES
+var Variables = require("./Variables"),
+    VariablesParseState = require("./VariablesParseState");
+
+// PROTOTYPE MEMBERS
+
+
+klass.parse = function parse(expr, vpsIn){
+    if(!("$map" in expr)) {
+        throw new Error("Tried to create a $let with something other than let. Looks like your parse map went all funny.");
+    }
+
+    if(typeof(expr.$map) !== 'object' || (expr.$map instanceof Array)) {
+        throw new Error("$map only supports an object as it's argument:16878");
+    }
+
+    var args = expr.$map,
+        inputElem = args.input,
+        inElem = args['in'],
+        asElem = args.as;
+
+    if(!inputElem) {
+        throw new Error("Missing 'input' parameter to $map: 16880");
+    }
+    if(!asElem) {
+        throw new Error("Missing 'as' parameter to $map: 16881");
+    }
+    if(!inElem) {
+        throw new Error("Missing 'in' parameter to $let: 16882");
+    }
+
+
+    if(Object.keys(args).length > 3) {
+        var bogus = Object.keys(args).filter(function(x) {return !(x === 'in' || x === 'as' || x === 'input');});
+        throw new Error("Unrecognized parameter to $map: " + bogus.join(",") + "- 16879");
+    }
+
+    var input = Expression.parseOperand(inputElem, vpsIn);
+
+    var vpsSub = new VariablesParseState(vpsIn),
+        varName = asElem;
+
+    Variables.uassertValidNameForUserWrite(varName);
+    var varId = vpsSub.defineVariable(varName);
+
+    var invert = Expression.parseOperand(inElem, vpsSub);
+
+    return new MapExpression(varName, varId, input, invert);
+};
+
+
+proto.optimize = function optimize() {
+    this._input = this._input.optimize();
+    this._each = this._each.optimize();
+    return this;
+};
+
+proto.serialize = function serialize(explain) {
+    return {$map: {input:this._input.serialize(explain),
+                   as: this._varName,
+                   'in': this._each.serialize(explain)}};
+};
+
+proto.evaluateInternal = function evaluateInternal(vars) {
+    var inputVal = this._input.evaluateInternal(vars);
+    if( inputVal === null) {
+        return null;
+    }
+
+    if(!(inputVal instanceof Array)) {
+        throw new Error("Input to $map must be an Array, not a ____ 16883");
+    }
+
+    if(inputVal.length === 0) {
+        return [];
+    }
+
+    // Diverge from Mongo source here, as Javascript has a builtin map operator.
+    return inputVal.map(function(x) {
+       vars.setValue(this._varId, x);
+       var toInsert = this._each.evaluateInternal(vars);
+       if(toInsert === undefined) {
+           toInsert = null;
+       }
+
+       return toInsert;
+   });
+};
+
+proto.addDependencies = function addDependencies(deps, path){
+    this._input.addDependencies(deps, path);
+    this._each.addDependencies(deps, path);
+    return deps;
+};
+
+
+Expression.registerExpression("$map", klass.parse);

+ 9 - 9
lib/pipeline/expressions/ModExpression.js

@@ -1,6 +1,6 @@
 "use strict";
 
-/** 
+/**
  * An $mod pipeline expression.
  * @see evaluate
  * @class ModExpression
@@ -13,12 +13,12 @@ var ModExpression = module.exports = function ModExpression() {
 		if (arguments.length !== 0) throw new Error("zero args expected");
 		base.call(this);
 }, klass = ModExpression,
-		base = require("./NaryExpression"),
-		proto = klass.prototype = Object.create(base.prototype, {
-				constructor: {
-						value: klass
-				}
-		});
+    base = require("./NaryExpression"),
+    proto = klass.prototype = Object.create(base.prototype, {
+        constructor: {
+            value: klass
+	}
+    });
 
 // DEPENDENCIES
 var Value = require("../Value"),
@@ -29,7 +29,7 @@ proto.getOpName = function getOpName() {
 		return "$mod";
 };
 
-/** 
+/**
  * Takes an array that contains a pair of numbers and returns the remainder of the first number divided by the second number.
  * @method evaluate
  **/
@@ -52,4 +52,4 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 };
 
 /** Register Expression */
-Expression.registerExpression("$mod", ModExpression.parse);
+Expression.registerExpression("$mod", base.parse(ModExpression));

+ 13 - 3
lib/pipeline/expressions/NaryExpression.js

@@ -11,11 +11,21 @@
 var Expression = require("./Expression");
 
 var NaryExpression = module.exports = function NaryExpression(){
-	if (arguments.length !== 0) throw new Error("zero args expected");
-	this.operands = [];
-	base.call(this);
+    if (arguments.length !== 0) throw new Error("Zero args expected");
+    this.operands = [];
+    base.call(this);
 }, klass = NaryExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
+klass.parse = function(SubClass) {
+    return function parse(expr, vps) {
+        var outExpr = new SubClass(),
+            args = NaryExpression.parseArguments(expr, vps);
+        outExpr.validateArguments(args);
+        outExpr.operands = args;
+        return outExpr;
+    };
+};
+
 klass.parseArguments = function(exprElement, vps) {
     var out = [];
     if(exprElement instanceof Array) {

+ 11 - 12
lib/pipeline/expressions/SizeExpression.js

@@ -1,6 +1,6 @@
 "use strict";
 
-/** 
+/**
  * A $size pipeline expression.
  * @see evaluateInternal
  * @class SizeExpression
@@ -9,16 +9,15 @@
  * @constructor
  **/
 var SizeExpression = module.exports = function SizeExpression() {
-		this.fixedArity(1);
-		if (arguments.length !== 1) throw new Error("one arg expected");
-		base.call(this);
+    this.fixedArity(1);
+    base.call(this);
 }, klass = SizeExpression,
-		base = require("./NaryExpression"),
-		proto = klass.prototype = Object.create(base.prototype, {
-				constructor: {
-						value: klass
-				}
-		});
+    base = require("./NaryExpression"),
+    proto = klass.prototype = Object.create(base.prototype, {
+        constructor: {
+            value: klass
+	}
+    });
 
 // DEPENDENCIES
 var Value = require("../Value"),
@@ -29,7 +28,7 @@ proto.getOpName = function getOpName() {
 		return "$size";
 };
 
-/** 
+/**
  * Takes an array and return the size.
  **/
 proto.evaluateInternal = function evaluateInternal(vars) {
@@ -39,4 +38,4 @@ proto.evaluateInternal = function evaluateInternal(vars) {
 };
 
 /** Register Expression */
-Expression.registerExpression("$size", klass.parse(SizeExpression));
+Expression.registerExpression("$size", base.parse(SizeExpression));

+ 11 - 20
test/lib/pipeline/expressions/ModExpression.js

@@ -1,7 +1,8 @@
 "use strict";
 var assert = require("assert"),
-	ModExpression = require("../../../../lib/pipeline/expressions/ModExpression"),
-	Expression = require("../../../../lib/pipeline/expressions/Expression");
+    ModExpression = require("../../../../lib/pipeline/expressions/ModExpression"),
+    Expression = require("../../../../lib/pipeline/expressions/Expression"),
+    VariablesParseState = require("../../../../lib/pipeline/expressions/Expression");
 
 
 module.exports = {
@@ -19,38 +20,28 @@ module.exports = {
 		},
 
 		"#getOpName()": {
-
 			"should return the correct op name; $mod": function testOpName(){
 				assert.equal(new ModExpression().getOpName(), "$mod");
 			}
 
 		},
 
-		"#getFactory()": {
-
-			"should return the constructor for this class": function factoryIsConstructor(){
-				assert.strictEqual(new ModExpression().getFactory(), undefined);
-			}
-
-		},
-
 		"#evaluateInternal()": {
-
 			"should return rhs if rhs is undefined or null": function testStuff(){
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:20.453, rhs:null}), null);
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:20.453}), undefined);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}, new VariablesParseState()).evaluate({lhs:20.453, rhs:null}), null);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:20.453}), undefined);
 			},
 			"should return lhs if lhs is undefined or null": function testStuff(){
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:null, rhs:20.453}), null);
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({rhs:20.453}), undefined);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:null, rhs:20.453}), null);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({rhs:20.453}), undefined);
 			},
 			"should return undefined if rhs is 0": function testStuff(){
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:20.453, rhs:0}), undefined);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:20.453, rhs:0}), undefined);
 			},
 			"should return proper mod of rhs and lhs if both are numbers": function testStuff(){
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:234.4234, rhs:45}), 234.4234 % 45);
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:0, rhs:45}), 0 % 45);
-				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluateInternal({lhs:-6, rhs:-0.5}), -6 % -0.5);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:234.4234, rhs:45}), 234.4234 % 45);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:0, rhs:45}), 0 % 45);
+				assert.strictEqual(Expression.parseOperand({$mod:["$lhs", "$rhs"]}).evaluate({lhs:-6, rhs:-0.5}), -6 % -0.5);
 			}
 
 		}