| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- "use strict";
- var Expression = require("./Expression"),
- Variables = require("./Variables"),
- FieldPath = require("../FieldPath");
- /**
- * Create a field path expression.
- *
- * Evaluation will extract the value associated with the given field
- * path from the source document.
- *
- * @class FieldPathExpression
- * @namespace mungedb-aggregate.pipeline.expressions
- * @module mungedb-aggregate
- * @extends mungedb-aggregate.pipeline.expressions.Expression
- * @constructor
- * @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}});
- /**
- * 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
- /**
- * Like create(), but works with the raw string from the user with the "$" prefixes.
- * @param raw raw string fieldpath
- * @param vps variablesParseState
- * @returns a new FieldPathExpression
- */
- klass.parse = function parse(raw, vps) {
- 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 $$
- dotIndex = fieldPath.indexOf("."),
- varName = fieldPath.substr(0, dotIndex !== -1 ? dotIndex : fieldPath.length);
- Variables.uassertValidNameForUserRead(varName);
- return new FieldPathExpression(fieldPath, vps.getVariable(varName));
- } else {
- return new FieldPathExpression("CURRENT." + raw.substr(1), // strip the "$" prefix
- vps.getVariable("CURRENT"));
- }
- };
- proto.optimize = function optimize() {
- // nothing can be done for these
- return this;
- };
- 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;
- }
- }
- };
- /**
- * Helper for evaluatePath to handle Array case
- */
- proto._evaluatePathArray = function _evaluatePathArray(index, input) {
- if (!(input instanceof Array)) throw new Error("must be array; dassert");
- // Check for remaining path in each element of array
- var result = [];
- for (var i = 0, l = input.length; i < l; i++) {
- if (!(input[i] instanceof Object))
- continue;
- var nested = this._evaluatePath(index, input[i]);
- if (nested !== undefined)
- result.push(nested);
- }
- return result;
- };
- /**
- * 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 {
- 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());
- }
- 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 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 {
- return "$$" + this._fieldPath.getPath(false);
- }
- };
- proto.getFieldPath = function getFieldPath(){
- return this._fieldPath;
- };
|