| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- "use strict";
- /**
- * 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} 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);
- };
- // 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;
- }
- };
- /**
- * Parses a fieldpath using the mongo 2.5 spec with optional variables
- *
- * @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 $");
- }
- 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));
- } else {
- return new FieldPathExpression("CURRENT." + raw.slice(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() {
- 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.evaluatePathArray = function evaluatePathArray(index, input) {
- if(!(input instanceof Array)) {
- throw new Error("evaluatePathArray called on non-array");
- }
- var result = [];
- 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);
- }
- }
- }
- 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);
- } else {
- return undefined;
- }
- };
- 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[""] = 1;
- } else {
- deps[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);
- };
- proto.serialize = function toJSON(){
- if(this.path.fields[0] === "CURRENT" && this.path.fields.length > 1) {
- return "$" + this.path.tail().getPath(false);
- } else {
- return "$$" + this.path.getPath(false);
- }
- };
- //TODO: proto.addToBsonObj = ...?
- //TODO: proto.addToBsonArray = ...?
- //proto.writeFieldPath = ...? use #getFieldPath instead
|