FieldPathExpression.js 3.3 KB

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