FieldPathExpression.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. "use strict";
  2. /**
  3. * Create a field path expression. Evaluation will extract the value associated with the given field path from the source document.
  4. * @class FieldPathExpression
  5. * @namespace mungedb-aggregate.pipeline.expressions
  6. * @module mungedb-aggregate
  7. * @extends mungedb-aggregate.pipeline.expressions.Expression
  8. * @constructor
  9. * @param {String} fieldPath the field path string, without any leading document indicator
  10. **/
  11. var FieldPathExpression = module.exports = function FieldPathExpression(path){
  12. if (arguments.length !== 1) throw new Error("args expected: path");
  13. this.path = new FieldPath(path);
  14. }, klass = FieldPathExpression, base = require("./Expression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  15. // DEPENDENCIES
  16. var FieldPath = require("../FieldPath");
  17. // PROTOTYPE MEMBERS
  18. proto.evaluate = function evaluate(obj){
  19. return this._evaluatePath(obj, 0, this.path.fields.length);
  20. };
  21. /**
  22. * Internal implementation of evaluate(), used recursively.
  23. *
  24. * The internal implementation doesn't just use a loop because of the
  25. * possibility that we need to skip over an array. If the path is "a.b.c",
  26. * and a is an array, then we fan out from there, and traverse "b.c" for each
  27. * element of a:[...]. This requires that a be an array of objects in order
  28. * to navigate more deeply.
  29. *
  30. * @param index current path field index to extract
  31. * @param pathLength maximum number of fields on field path
  32. * @param pDocument current document traversed to (not the top-level one)
  33. * @returns the field found; could be an array
  34. **/
  35. proto._evaluatePath = function _evaluatePath(obj, i, len){
  36. var fieldName = this.path.fields[i],
  37. field = obj[fieldName]; // It is possible we won't have an obj (document) and we need to not fail if that is the case
  38. // if the field doesn't exist, quit with an undefined value
  39. if (field === undefined) return undefined;
  40. // if we've hit the end of the path, stop
  41. if (++i >= len) return field;
  42. // We're diving deeper. If the value was null, return null
  43. if(field === null) return undefined;
  44. if (field.constructor === Object) {
  45. return this._evaluatePath(field, i, len);
  46. } else if (Array.isArray(field)) {
  47. var results = [];
  48. for (var i2 = 0, l2 = field.length; i2 < l2; i2++) {
  49. var subObj = field[i2],
  50. subObjType = typeof(subObj);
  51. if (subObjType === "undefined" || subObj === null) {
  52. results.push(subObj);
  53. } else if (subObj.constructor === Object) {
  54. results.push(this._evaluatePath(subObj, i, len));
  55. } else {
  56. throw new Error("the element '" + fieldName + "' along the dotted path '" + this.path.getPath() + "' is not an object, and cannot be navigated.; code 16014");
  57. }
  58. }
  59. return results;
  60. }
  61. return undefined;
  62. };
  63. proto.optimize = function(){
  64. return this;
  65. };
  66. proto.addDependencies = function addDependencies(deps){
  67. deps[this.path.getPath()] = 1;
  68. return deps;
  69. };
  70. // renamed write to get because there are no streams
  71. proto.getFieldPath = function getFieldPath(usePrefix){
  72. return this.path.getPath(usePrefix);
  73. };
  74. proto.toJSON = function toJSON(){
  75. return this.path.getPath(true);
  76. };
  77. //TODO: proto.addToBsonObj = ...?
  78. //TODO: proto.addToBsonArray = ...?
  79. //proto.writeFieldPath = ...? use #getFieldPath instead