ソースを参照

Refs #5126: Add initial Redact stuff to Matcher

Chris Sexton 11 年 前
コミット
3ceb9ba21e
1 ファイル変更166 行追加1 行削除
  1. 166 1
      lib/pipeline/documentSources/MatchDocumentSource.js

+ 166 - 1
lib/pipeline/documentSources/MatchDocumentSource.js

@@ -106,5 +106,170 @@ proto.getQuery = function getQuery() {
  *  that have had their age field removed.
  */
 proto.redactSafePortion = function redactSafePortion() {
-	throw new Error("not implemented");
+	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
+	// input is well formed.
+
+	var isAllDigits = function(n) {
+		return typeof n == 'number' && !isNaN(n - n);
+	};
+
+	var isFieldnameRedactSafe = function isFieldnameRedactSafe(field) {
+		var dotPos = field.indexOf('.');
+		if (dotPos === -1)
+			return !isAllDigits(field);
+
+		var part = field.slice(0, dotPos),
+			rest = field.slice(dotPos+1, field.length);
+
+		return !isAllDigits(part) && isFieldnameRedactSafe(rest);
+	};
+
+	// 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 output = {},
+			elem,i,j,k;
+
+		var keys = Object.keys(expr);
+		for (i = 0; i < keys.length; i++) {
+			var field = keys[i],
+				value = expr[field];
+
+			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') {
+				output[field] = value;
+			} else if (field === '$lte' || field === '$gte' || field === '$lt' || field === '$gt') {
+				if (isTypeRedactSaveInComparison(field))
+					output[field] = value;
+			} 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); j++) {
+					elem = Object.keys(value)[j];
+					if (!isTypeRedactSaveInComparison(elem)) {
+						allOk = false;
+						break;
+					}
+				}
+				if (allOk) {
+					output[field] = value;
+				}
+				break;
+			} else if (field === '$all') {
+				// TODO: value/elem/field/etc may be mixed up and wrong here
+				var matches = [];
+				for (j = 0; j < field.length; j++) {
+					elem = Object.keys(value)[j];
+					if (isTypeRedactSaveInComparison(elem))
+						matches.push(value[elem]);
+				}
+				if (matches.length)
+					output[field] = matches;
+
+			} else if (field === '$elemMatch') {
+				var subIn = field,
+					subOut;
+
+				if (subIn[0] === '$')
+					subOut = redactSafePortionDollarOps(subIn);
+				else
+					subOut = redactSafePortionTopLevel(subIn);
+
+				if (subOut)
+					output[field] = subOut;
+
+				break;
+			} else {
+				// never allowed:
+				// equality, maxDist, near, ne, opSize, nin, exists, within, geoIntersects
+				continue;
+			}
+		}
+
+		return output;
+	};
+
+	var isTypeRedactSaveInComparison = function isTypeRedactSaveInComparison(type) {
+		if (type instanceof Array || type instanceof Object || type === null || type === undefined)
+			return false;
+		return true;
+	};
+
+	// 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 output = {},
+			okClauses = [],
+			keys = Object.keys(query),
+			j, elm, clause;
+
+		for (var i = 0; i < keys.length; i++) {
+			var field = keys[i],
+				query = topQuery[field];
+
+			if (field.length && field[0] === '$') {
+				if (field === '$or') {
+					okClauses = [];
+					for (j = 0; j < Object.keys(field).length; j++) {
+						elm = field[Object.keys(field)[j]];
+						clause = redactSafePortionTopLevel(elm);
+
+						if (!clause) {
+							okClauses = [];
+							break;
+						}
+
+						okClauses.push(clause);
+					}
+
+					if (okClauses) {
+						output.$or = okClauses;
+					}
+				} else if (field === '$and') {
+					okClauses = [];
+					for (j = 0; j < Object.keys(field).length; j++) {
+						elm = field[Object.keys(field)[j]];
+						clause = redactSafePortionTopLevel(elm);
+
+						if (clause)
+							okClauses.push(clause);
+					}
+
+					if (okClauses.length)
+						output.$and = okClauses;
+				}
+
+				continue;
+			}
+
+			if (!isFieldnameRedactSafe(field))
+					continue;
+
+			if (field instanceof Array || !field) {
+				continue;
+			} else {
+				// subobjects
+				var sub = redactSavePortionDollarOps(field);
+				if (sub)
+					output[field] = sub;
+
+				break;
+			}
+		}
+
+		return output;
+	};
+
+	return redactSafePortionTopLevel(this.getQuery());
 };