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

EAGLESIX-2651: FieldPath: fix expr to match 2.6.5 code

Kyle P Davis 11 лет назад
Родитель
Сommit
11f86b5b63
1 измененных файлов с 114 добавлено и 161 удалено
  1. 114 161
      lib/pipeline/expressions/FieldPathExpression.js

+ 114 - 161
lib/pipeline/expressions/FieldPathExpression.js

@@ -1,207 +1,160 @@
 "use strict";
 "use strict";
 
 
+var Expression = require("./Expression"),
+    Variables = require("./Variables"),
+    Value = require("../Value"),
+    FieldPath = require("../FieldPath");
+
 /**
 /**
- * Create a field path expression. Evaluation will extract the value associated with the given field path from the source document.
+ * Create a field path expression.
+ *
+ * Evaluation will extract the value associated with the given field
+ * path from the source document.
+ *
  * @class FieldPathExpression
  * @class FieldPathExpression
  * @namespace mungedb-aggregate.pipeline.expressions
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @module mungedb-aggregate
  * @extends mungedb-aggregate.pipeline.expressions.Expression
  * @extends mungedb-aggregate.pipeline.expressions.Expression
  * @constructor
  * @constructor
- * @param {String} fieldPath the field path string, without any leading document indicator
- **/
-
-var Expression = require("./Expression"),
-    Variables = require("./Variables"),
-    Value = require("../Value"),
-    FieldPath = require("../FieldPath");
-
-
-var FieldPathExpression = module.exports = function FieldPathExpression(path, variableId){
-    if (arguments.length > 2) throw new Error("args expected: path[, vps]");
-    this.path = new FieldPath(path);
-    if(arguments.length == 2) {
-        this.variable = variableId;
-    } else {
-        this.variable = Variables.ROOT_ID;
-    }
-}, klass = FieldPathExpression, base = require("./Expression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
-
-klass.create = function create(path) {
-    return new FieldPathExpression("CURRENT."+path, Variables.ROOT_ID);
-};
+ * @param {String} theFieldPath the field path string, without any leading document indicator
+ */
+var FieldPathExpression = module.exports = function FieldPathExpression(theFieldPath, variable) {
+    if (arguments.length != 2) throw new Error(klass.name + ": expected args: theFieldPath[, variable]");
+    this._fieldPath = new FieldPath(theFieldPath);
+    this._variable = variable;
+}, klass = FieldPathExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
 
-
-// PROTOTYPE MEMBERS
-proto.evaluateInternal = function evaluateInternal(vars){
-
-    if(this.path.fields.length === 1) {
-        return vars.getValue(this.variable);
-    }
-
-    if(this.variable === Variables.ROOT_ID) {
-        return this.evaluatePath(1, vars.getRoot());
-    }
-
-    var vari = vars.getValue(this.variable);
-    if(vari instanceof Array) {
-        return this.evaluatePathArray(1,vari);
-    } else if (vari instanceof Object) {
-        return this.evaluatePath(1, vari);
-    } else {
-        return undefined;
-    }
+/**
+ * Create a field path expression using old semantics (rooted off of CURRENT).
+ *
+ * // NOTE: this method is deprecated and only used by tests
+ * // TODO remove this method in favor of parse()
+ *
+ * Evaluation will extract the value associated with the given field
+ * path from the source document.
+ *
+ * @param fieldPath the field path string, without any leading document
+ * indicator
+ * @returns the newly created field path expression
+ **/
+klass.create = function create(fieldPath) {
+    return new FieldPathExpression("CURRENT." + fieldPath, Variables.ROOT_ID);
 };
 };
 
 
-
+// this is the new version that supports every syntax
 /**
 /**
- * Parses a fieldpath using the mongo 2.5 spec with optional variables
- *
+ * Like create(), but works with the raw string from the user with the "$" prefixes.
  * @param raw raw string fieldpath
  * @param raw raw string fieldpath
  * @param vps variablesParseState
  * @param vps variablesParseState
  * @returns a new FieldPathExpression
  * @returns a new FieldPathExpression
- **/
+ */
 klass.parse = function parse(raw, vps) {
 klass.parse = function parse(raw, vps) {
-    if(raw[0] !== "$") {
-        throw new Error("FieldPath: '" + raw + "' doesn't start with a $");
-    }
-    if(raw.length === 1) {
-        throw new Error("'$' by itself is not a valid FieldPath");
-    }
-
-    if(raw[1] === "$") {
-        var firstPeriod = raw.indexOf('.');
-        var varname = (firstPeriod === -1 ? raw.slice(2) : raw.slice(2,firstPeriod));
-        Variables.uassertValidNameForUserRead(varname);
-        return new FieldPathExpression(raw.slice(2), vps.getVariableName(varname));
+    if (raw[0] !== "$") throw new Error("FieldPath: '" + raw + "' doesn't start with a $; uassert code 16873");
+    if (raw.length < 2) throw new Error("'$' by itself is not a valid FieldPath; uassert code 16872"); // need at least "$" and either "$" or a field name
+    if (raw[1] === "$") {
+        var fieldPath = raw.substr(2), // strip off $$
+            varName = fieldPath.substr(0, fieldPath.indexOf("."));
+        Variables.uassertValidNameForUserRead(varName);
+        return new FieldPathExpression(raw.slice(2), vps.getVariableName(varName));
     } else {
     } else {
-        return new FieldPathExpression("CURRENT." + raw.slice(1), vps.getVariable("CURRENT"));
+        return new FieldPathExpression("CURRENT." + raw.substr(1), vps.getVariable("CURRENT"));
     }
     }
 };
 };
 
 
-
-/**
- * Parses a fieldpath using the mongo 2.5 spec with optional variables
- *
- * @param raw raw string fieldpath
- * @param vps variablesParseState
- * @returns a new FieldPathExpression
- **/
 proto.optimize = function optimize() {
 proto.optimize = function optimize() {
+    // nothing can be done for these
     return this;
     return this;
 };
 };
 
 
-
-/**
- * Internal implementation of evaluate(), used recursively.
- *
- * The internal implementation doesn't just use a loop because of the
- * possibility that we need to skip over an array.  If the path is "a.b.c",
- * and a is an array, then we fan out from there, and traverse "b.c" for each
- * element of a:[...].  This requires that a be an array of objects in order
- * to navigate more deeply.
- *
- * @param index current path field index to extract
- * @param pathLength maximum number of fields on field path
- * @param pDocument current document traversed to (not the top-level one)
- * @returns the field found; could be an array
- **/
-proto._evaluatePath = function _evaluatePath(obj, i, len){
-	var fieldName = this.path.fields[i],
-		field = obj[fieldName]; // It is possible we won't have an obj (document) and we need to not fail if that is the case
-
-	// if the field doesn't exist, quit with an undefined value
-	if (field === undefined) return undefined;
-
-	// if we've hit the end of the path, stop
-	if (++i >= len) return field;
-
-	// We're diving deeper.  If the value was null, return null
-	if(field === null) return undefined;
-
-	if (field.constructor === Object) {
-		return this._evaluatePath(field, i, len);
-	} else if (Array.isArray(field)) {
-		var results = [];
-		for (var i2 = 0, l2 = field.length; i2 < l2; i2++) {
-			var subObj = field[i2],
-				subObjType = typeof(subObj);
-			if (subObjType === "undefined" || subObj === null) {
-				results.push(subObj);
-			} else if (subObj.constructor === Object) {
-				results.push(this._evaluatePath(subObj, i, len));
-			} else {
-				throw new Error("the element '" + fieldName + "' along the dotted path '" + this.path.getPath() + "' is not an object, and cannot be navigated.; code 16014");
-			}
-		}
-		return results;
-	}
-	return undefined;
+proto.addDependencies = function addDependencies(deps) {
+    if (this._variable === Variables.ROOT_ID) {
+        if (this._fieldPath.fieldNames.length === 1) {
+            deps.needWholeDocument = true; // need full doc if just "$$ROOT"
+        } else {
+            deps.fields[this._fieldPath.tail().getPath(false)] = 1;
+        }
+    }
 };
 };
 
 
-proto.evaluatePathArray = function evaluatePathArray(index, input) {
+/**
+ * Helper for evaluatePath to handle Array case
+ */
+proto._evaluatePathArray = function _evaluatePathArray(index, input) {
+    if (!(input instanceof Array)) throw new Error("must be array; dassert");
 
 
-    if(!(input instanceof Array)) {
-        throw new Error("evaluatePathArray called on non-array");
-    }
+    // Check for remaining path in each element of array
     var result = [];
     var result = [];
+    for (var i = 0, l = input.length; i < l; i++) {
+        if (!(input[i] instanceof Object))
+            continue;
 
 
-    for(var ii = 0; ii < input.length; ii++) {
-        if(input[ii] instanceof Object) {
-            var nested = this.evaluatePath(index, input[ii]);
-            if(nested) {
-				result.push(nested);
-            }
-        }
+        var nested = this._evaluatePath(index, input[i]);
+        if (nested !== undefined)
+            result.push(nested);
     }
     }
     return result;
     return result;
 };
 };
 
 
-
-proto.evaluatePath = function(index, input) {
-    if(index === this.path.fields.length -1) {
-        return input[this.path.fields[index]];
-    }
-    var val = input[this.path.fields[index]];
-    if(val instanceof Array) {
-        return this.evaluatePathArray(index+1, val);
-    } else if (val instanceof Object) {
-        return this.evaluatePath(index+1, val);
+/**
+ * Internal implementation of evaluateInternal(), used recursively.
+ *
+ * The internal implementation doesn't just use a loop because of
+ * the possibility that we need to skip over an array.  If the path
+ * is "a.b.c", and a is an array, then we fan out from there, and
+ * traverse "b.c" for each element of a:[...].  This requires that
+ * a be an array of objects in order to navigate more deeply.
+ *
+ * @param index current path field index to extract
+ * @param input current document traversed to (not the top-level one)
+ * @returns the field found; could be an array
+ */
+proto._evaluatePath = function _evaluatePath(index, input) {
+    // Note this function is very hot so it is important that is is well optimized.
+    // In particular, all return paths should support RVO.
+
+    // if we've hit the end of the path, stop
+    if (index == this._fieldPath.fieldNames.length - 1)
+        return input[this._fieldPath.fieldNames[index]];
+
+    // Try to dive deeper
+    var val = input[this._fieldPath.fieldNames[index]];
+    if (val instanceof Object && val.constructor === Object) {
+        return this._evaluatePath(index + 1, val);
+    } else if (val instanceof Array) {
+        return this._evaluatePathArray(index + 1, val);
     } else {
     } else {
         return undefined;
         return undefined;
     }
     }
-
 };
 };
 
 
+proto.evaluateInternal = function evaluateInternal(vars) {
+    if (this._fieldPath.fieldNames.length === 1) // get the whole variable
+        return vars.getValue(this._variable);
 
 
+    if (this._variable === Variables.ROOT_ID) {
+        // ROOT is always a document so use optimized code path
+        return this._evaluatePath(1, vars.getRoot());
+    }
 
 
-proto.optimize = function(){
-        return this;
-};
-
-proto.addDependencies = function addDependencies(deps){
-	if(this.path.fields[0] === "CURRENT" || this.path.fields[0] === "ROOT") {
-		if(this.path.fields.length === 1) {
-			deps.needWholeDocument = true;
-		} else {
-			deps.fields[this.path.tail().getPath(false)] = 1;
-		}
-	}
-};
-
-// renamed write to get because there are no streams
-proto.getFieldPath = function getFieldPath(usePrefix){
-        return this.path.getPath(usePrefix);
+    var val = vars.getValue(this._variable);
+    if (val instanceof Object && val.constructor === Object) {
+        return this._evaluatePath(1, val);
+    } else if(val instanceof Array) {
+        return this._evaluatePathArray(1,val);
+    } else {
+        return undefined;
+    }
 };
 };
 
 
-proto.serialize = function toJSON(){
-    if(this.path.fields[0] === "CURRENT" && this.path.fields.length > 1) {
-        return "$" + this.path.tail().getPath(false);
+proto.serialize = function serialize(){
+    if(this._fieldPath.fieldNames[0] === "CURRENT" && this._fieldPath.fieldNames.length > 1) {
+        // use short form for "$$CURRENT.foo" but not just "$$CURRENT"
+        return "$" + this._fieldPath.tail().getPath(false);
     } else {
     } else {
-        return "$$" + this.path.getPath(false);
+        return "$$" + this._fieldPath.getPath(false);
     }
     }
 };
 };
 
 
-//TODO: proto.addToBsonObj = ...?
-//TODO: proto.addToBsonArray = ...?
-
-//proto.writeFieldPath = ...?   use #getFieldPath instead
+proto.getFieldPath = function getFieldPath(){
+    return this._fieldPath;
+};