DepsTracker.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. "use strict";
  2. /**
  3. * Allows components in an aggregation pipeline to report what they need from their input.
  4. *
  5. * @class DepsTracker
  6. * @namespace mungedb-aggregate.pipeline
  7. * @module mungedb-aggregate
  8. * @constructor
  9. */
  10. var DepsTracker = module.exports = function DepsTracker() {
  11. // fields is a set of strings
  12. this.fields = {};
  13. this.needWholeDocument = false;
  14. this.needTextScore = false;
  15. }, klass = DepsTracker, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  16. var ParsedDeps = require("./ParsedDeps");
  17. /**
  18. * Returns a projection object covering the dependencies tracked by this class.
  19. * @method toProjection
  20. * @return {Object} projection of caller's dependencies
  21. */
  22. proto.toProjection = function toProjection() {
  23. var proj = {};
  24. // if(this.needTextScore) {
  25. // bb.append(Document::metaFieldTextScore, BSON("$meta" << "textScore"));
  26. // }
  27. if (this.needWholeDocument) {
  28. return proj;
  29. }
  30. if (Object.keys(this.fields).length === 0) {
  31. // Projection language lacks good a way to say no fields needed. This fakes it.
  32. proj._id = 0;
  33. proj.$noFieldsNeeded = 1;
  34. return proj;
  35. }
  36. var last = "",
  37. needId = false;
  38. Object.keys(this.fields).sort().forEach(function (it) {
  39. if (it.slice(0,3) == "_id" && (it.length == 3 || it.charAt(3) == ".")) {
  40. // _id and subfields are handled specially due in part to SERVER-7502
  41. needId = true;
  42. return;
  43. }
  44. if (last !== "" && it.slice(0, last.length) === last) {
  45. // we are including a parent of *it so we don't need to include this
  46. // field explicitly. In fact, due to SERVER-6527 if we included this
  47. // field, the parent wouldn't be fully included. This logic relies
  48. // on on set iterators going in lexicographic order so that a string
  49. // is always directly before of all fields it prefixes.
  50. return;
  51. }
  52. last = it + ".";
  53. proj[it] = 1;
  54. });
  55. if (needId)
  56. proj._id = 1;
  57. else
  58. proj._id = 0;
  59. return proj;
  60. };
  61. /**
  62. * Takes a depsTracker and builds a simple recursive lookup table out of it.
  63. * @method toParsedDeps
  64. * @return {ParsedDeps}
  65. */
  66. proto.toParsedDeps = function toParsedDeps() {
  67. var doc = {};
  68. if (this.needWholeDocument || this.needTextScore) {
  69. // can't use ParsedDeps in this case
  70. // TODO: not sure what appropriate equivalent to boost::none is
  71. return;
  72. }
  73. var last = "";
  74. Object.keys(this.fields).sort().forEach(function (it) {
  75. if (last !== "" && it.slice(0, last.length) === last) {
  76. // we are including a parent of *it so we don't need to include this
  77. // field explicitly. In fact, due to SERVER-6527 if we included this
  78. // field, the parent wouldn't be fully included. This logic relies
  79. // on on set iterators going in lexicographic order so that a string
  80. // is always directly before of all fields it prefixes.
  81. return;
  82. }
  83. last = it + ".";
  84. // TODO: set nested field to true; i.e. a.b.c = true, not a = true
  85. doc[it] = true;
  86. });
  87. return new ParsedDeps(doc);
  88. };