FieldPathExpression.js 2.9 KB

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