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

Merge branch 'feature/mongo_2.6.5_expressions_SetIsSubset' into feature/mongo_2.6.5_expressions_SetIntersection

Jake Delaney 11 лет назад
Родитель
Сommit
eb4eda1476

+ 25 - 0
lib/pipeline/expressions/Helpers.js

@@ -0,0 +1,25 @@
+module.exports = {
+	//Returns an object containing unique values. All keys are the same as the corresponding value.
+	arrayToSet : function arrayToSet(array){
+
+		var set = {};
+
+		// This ensures no duplicates.
+		array.forEach(function (element) {
+			var elementString = JSON.stringify(element);
+			set[elementString] = element;
+		});
+
+		return set;
+	},
+
+	setToArray: function setToArray(set){
+		var array = [];
+
+		Object.keys(set).forEach(function (key) {
+			array.push(set[key]);
+		});
+
+		return array;
+	}
+}

+ 66 - 43
lib/pipeline/expressions/SetIsSubsetExpression.js

@@ -8,8 +8,9 @@
  * @module mungedb-aggregate
  * @constructor
  **/
+
 var SetIsSubsetExpression = module.exports = function SetIsSubsetExpression() {
-	if (arguments.length !== 2) throw new Error("two args expected");
+//	if (arguments.length !== 2) throw new Error("two args expected");
 	base.call(this);
 }, klass = SetIsSubsetExpression,
 	FixedArityExpression = require("./FixedArityExpressionT")(klass, 2),
@@ -20,68 +21,90 @@ var SetIsSubsetExpression = module.exports = function SetIsSubsetExpression() {
 		}
 	});
 
+
 // DEPENDENCIES
 var Value = require("../Value"),
-	Expression = require("./Expression");
+	Expression = require("./Expression"),
+	Helpers = require("./Helpers");
 
 // PROTOTYPE MEMBERS
 proto.getOpName = function getOpName() {
 	return "$setissubset";
 };
 
-proto.optimize = function optimize(cachedRhsSet, operands) {
 
-// This optimize needs to be done, eventually
+// lhs should be array, rhs should be set (object). See arrayToSet implementation.
+var setIsSubsetHelper = function setIsSubsetHelper(lhs, rhs){
+	var lset = Helpers.arrayToSet(lhs);
+		rkeys = Object.keys(rhs);
+	// do not shortcircuit when lhs.size() > rhs.size()
+	// because lhs can have redundant entries
+	Object.keys(lset).forEach(function (lkey){
+		if (rkeys.indexOf(lkey) < 0){
+			return false;
+		}
+	});
+
+	return true;
+};
+
+/**
+ * Takes 2 arrays. Returns true if the first is a subset of the second. Returns false otherwise.
+ * @method evaluateInternal
+ **/
+proto.evaluateInternal = function evaluateInternal(vars) {
+	var lhs = this.operands[0].evaluateInternal(vars),
+		rhs = this.operands[1].evaluateInternal(vars);
+
+	if (!(lhs instanceof Array)) throw new Error("Both operands of " + this.getOpName() + ": be arrays. First argument is of type " + typeof lhs);
+	if (!(rhs instanceof Array)) throw new Error("Both operands of " + this.getOpName() + ": be arrays. First argument is of type " + typeof rhs);
+
+	return setIsSubsetHelper(lhs, Helpers.arrayToSet(rhs));
+};
 
-// // perfore basic optimizations
-//     intrusive_ptr<Expression> optimized = ExpressionNary::optimize();
 
-//     // if ExpressionNary::optimize() created a new value, return it directly
-//     if (optimized.get() != this)
-//         return optimized;
 
-//     if (ExpressionConstant* ec = dynamic_cast<ExpressionConstant*>(vpOperand[1].get())) {
-//         const Value rhs = ec->getValue();
-//         uassert(17311, str::stream() << "both operands of $setIsSubset must be arrays. Second "
-//                                      << "argument is of type: " << typeName(rhs.getType()),
-//                 rhs.getType() == Array);
+var Optimized = function Optimized(cachedRhsSet, operands) {
+	this.operands = operands;
+	this._cachedRhsSet = cachedRhsSet;
+}
 
-//         return new Optimized(arrayToSet(rhs), vpOperand);
-//     }
+Optimized.prototype = Object.create(SetIsSubsetExpression.prototype, {
+	constructor: {
+		value: Optimized
+	}
+});
 
-//     return optimized;
+Optimized.prototype.evaluateInternal = function evaluateInternal(vars){
+	lhs = this.operands[0].evaluateInternal(vars);
 
+	if (!(lhs instanceof Array)) throw new Error("uassert 17310: both operands of " + this.getOpName() + "  must be arrays. First argument is of type " + typeof lhs);
+	
+	return setIsSubsetHelper(lhs, this._cachedRhsSet);
 };
 
-/**
- * Takes 2 arrays. Assigns the second array to the first array.
- * @method evaluateInternal
- **/
-proto.evaluateInternal = function evaluateInternal(vars) {
-	var array1 = this.operands[0].evaluateInternal(vars),
-		array2 = this.operands[1].evaluateInternal(vars);
-	if (array1 instanceof Array) throw new Error(this.getOpName() + ": object 1 must be an array");
-	if (array2 instanceof Array) throw new Error(this.getOpName() + ": object 2 must be an array");
-
-	var sizeOfArray1 = array1.length;
-	var sizeOfArray2 = array2.length;
-	var outerLoop = 0;
-	var innerLoop = 0;
-	for (outerLoop = 0; outerLoop < sizeOfArray1; outerLoop++) {
-		for (innerLoop = 0; innerLoop < sizeOfArray2; innerLoop++) {
-			if (array2[outerLoop] == array1[innerLoop])
-				break;
-		}
 
-		/* If the above inner loop was not broken at all then
-		 array2[i] is not present in array1[] */
-		if (innerLoop == sizeOfArray2)
-			return false;
+proto.optimize = function optimize(cachedRhsSet, operands) {
+	// perform basic optimizations
+	//var optimized = base.optimize.call(this);
+	var optimized = NaryExpression.optimize();
+
+	// if NaryExpression.optimize() created a new value, return it directly
+	if(optimized != this){
+		return optimized;
 	}
 
-	/* If we reach here then all elements of array2[]
-	 are present in array1[] */
-	return true;
+	if (this.operands[1] instanceof ConstantExpression){
+		var ce = this.operands[1],
+			rhs = ce.getValue();
+
+		if (!(rhs instanceof Array)) throw new Error("uassert 17311: both operands of " + this.getOpName() + "  must be arrays. Second argument is of type " + typeof rhs);
+
+		return new Optimized(Helpers.arrayToSet(rhs), this.operands);
+	}
+
+	return optimized;
+
 };
 
 /** Register Expression */

+ 11 - 0
test/lib/pipeline/expressions/SetIsSubsetExpression.js

@@ -78,6 +78,17 @@ module.exports = {
 								}), true);
 						},
 
+						"Should pass and return a true2": function testBasicAssignment(){
+							var array1 = [1, 2, 3, 4, 5],
+								array2 = [2,3],
+								input = ["$array1","$array2"],
+							 	expr = Expression.parseExpression("$setissubset", input),
+								result = expr.evaluate({}),
+								expected = true,
+								msg = errMsg("$allElementsTrue", input, expr.serialize(false), expected, result);
+							assert.equal(result, expected, msg);
+						},
+
 						"Should pass and return false": function testBasicAssignment() {
 								var array1 = [1, 2, 3, 4, 5],
 										array2 = [7, 8, 9];