UnwindDocumentSource.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. "use strict";
  2. var async = require("async"),
  3. DocumentSource = require("./DocumentSource"),
  4. Expression = require("../expressions/Expression"),
  5. FieldPath = require("../FieldPath"),
  6. Document = require("../Document");
  7. /**
  8. * A document source unwinder
  9. * @class UnwindDocumentSource
  10. * @namespace mungedb-aggregate.pipeline.documentSources
  11. * @module mungedb-aggregate
  12. * @constructor
  13. * @param [ctx] {ExpressionContext}
  14. **/
  15. var UnwindDocumentSource = module.exports = function UnwindDocumentSource(ctx){
  16. if (arguments.length > 1) {
  17. throw new Error("Up to one argument expected.");
  18. }
  19. base.call(this, ctx);
  20. this._unwindPath = null; // Configuration state.
  21. this._unwinder = null; // Iteration state.
  22. }, klass = UnwindDocumentSource, base = DocumentSource, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}}); //jshint ignore:line
  23. klass.unwindName = "$unwind";
  24. klass.Unwinder = (function() {
  25. /**
  26. * Construct a new Unwinder instance. Used as a parent class for UnwindDocumentSource.
  27. *
  28. * @param unwindPath
  29. * @constructor
  30. */
  31. var klass = function Unwinder(unwindPath) {
  32. this._unwindPath = new FieldPath(unwindPath);
  33. this._inputArray = undefined;
  34. this._document = undefined;
  35. this._index = undefined;
  36. }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor: {value: klass}});
  37. proto.resetDocument = function resetDocument(document) {
  38. if (!document) throw new Error("Document is required!");
  39. this._inputArray = [];
  40. this._document = document;
  41. this._index = 0;
  42. var pathValue = Document.getNestedField(this._document, this._unwindPath);
  43. if (!pathValue || pathValue.length === 0) {
  44. return;
  45. }
  46. if (!(pathValue instanceof Array)) {
  47. throw new Error(UnwindDocumentSource.unwindName + ": value at end of field path must be an array; code 15978");
  48. }
  49. this._inputArray = pathValue;
  50. };
  51. /**
  52. * getNext
  53. *
  54. * This is just wrapping the old functions because they are somewhat different
  55. * than the original mongo implementation, but should get updated to follow the current API.
  56. **/
  57. proto.getNext = function getNext() {
  58. if (this._inputArray === undefined || this._index === this._inputArray.length) {
  59. return null;
  60. }
  61. this._document = Document.cloneDeep(this._document);
  62. Document.setNestedField(this._document, this._unwindPath, this._inputArray[this._index++]);
  63. return this._document;
  64. };
  65. return klass;
  66. })();
  67. /**
  68. * Get the document source name.
  69. *
  70. * @method getSourceName
  71. * @returns {string}
  72. */
  73. proto.getSourceName = function getSourceName() {
  74. return klass.unwindName;
  75. };
  76. /**
  77. * Get the next source.
  78. *
  79. * @method getNext
  80. * @param callback
  81. * @returns {*}
  82. */
  83. proto.getNext = function getNext() {
  84. if (this.expCtx && this.expCtx.checkForInterrupt) this.expCtx.checkForInterrupt();
  85. var out = this._unwinder.getNext();
  86. while (!out) {
  87. // No more elements in array currently being unwound. This will loop if the input
  88. // document is missing the unwind field or has an empty array.
  89. var input = this.source.getNext();
  90. if (!input)
  91. return null; // input exhausted
  92. // Try to extract an output document from the new input document.
  93. this._unwinder.resetDocument(input);
  94. out = this._unwinder.getNext();
  95. }
  96. return out;
  97. };
  98. /**
  99. * Serialize the data.
  100. *
  101. * @method serialize
  102. * @param explain
  103. * @returns {{}}
  104. */
  105. proto.serialize = function serialize(explain) {
  106. if (!this._unwindPath) {
  107. throw new Error("unwind path does not exist!");
  108. }
  109. var doc = {};
  110. doc[this.getSourceName()] = this._unwindPath.getPath(true);
  111. return doc;
  112. };
  113. /**
  114. * Get the fields this operation needs to do its job.
  115. *
  116. * @method getDependencies
  117. * @param deps
  118. * @returns {DocumentSource.GetDepsReturn.SEE_NEXT|*}
  119. */
  120. proto.getDependencies = function getDependencies(deps) {
  121. if (!this._unwindPath) {
  122. throw new Error("unwind path does not exist!");
  123. }
  124. deps.fields[this._unwindPath.getPath(false)] = 1;
  125. return DocumentSource.GetDepsReturn.SEE_NEXT;
  126. };
  127. /**
  128. * Unwind path.
  129. *
  130. * @method unwindPath
  131. * @param fieldPath
  132. */
  133. proto.unwindPath = function unwindPath(fieldPath) {
  134. if (this._unwindPath) {
  135. throw new Error(this.getSourceName() + " can't unwind more than one path; code 15979");
  136. }
  137. // Record the unwind path.
  138. this._unwindPath = new FieldPath(fieldPath);
  139. this._unwinder = new klass.Unwinder(fieldPath);
  140. };
  141. /**
  142. * Creates a new UnwindDocumentSource with the input path as the path to unwind
  143. * @method createFromJson
  144. * @param {String} JsonElement this thing is *called* Json, but it expects a string
  145. **/
  146. klass.createFromJson = function createFromJson(jsonElement, ctx) {
  147. if (jsonElement.constructor !== String) {
  148. throw new Error("the " + klass.unwindName + " field path must be specified as a string; code 15981");
  149. }
  150. var pathString = Expression.removeFieldPrefix(jsonElement),
  151. unwind = new UnwindDocumentSource(ctx);
  152. unwind.unwindPath(pathString);
  153. return unwind;
  154. };