|
|
@@ -66,33 +66,12 @@ proto.getPipelineStep = function getPipelineStep() {
|
|
|
return this.step;
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * Is the source at EOF?
|
|
|
- * @method eof
|
|
|
- **/
|
|
|
-proto.eof = function eof() {
|
|
|
- throw new Error("not implemented");
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Advance the state of the DocumentSource so that it will return the next Document.
|
|
|
- * The default implementation returns false, after checking for interrupts.
|
|
|
- * Derived classes can call the default implementation in their own implementations in order to check for interrupts.
|
|
|
- *
|
|
|
- * @method advance
|
|
|
- * @returns {Boolean} whether there is another document to fetch, i.e., whether or not getCurrent() will succeed. This default implementation always returns false.
|
|
|
- **/
|
|
|
-proto.advance = function advance() {
|
|
|
- //pExpCtx->checkForInterrupt(); // might not return
|
|
|
- return false;
|
|
|
-};
|
|
|
-
|
|
|
/**
|
|
|
* some implementations do the equivalent of verify(!eof()) so check eof() first
|
|
|
- * @method getCurrent
|
|
|
+ * @method getNExt
|
|
|
* @returns {Document} the current Document without advancing
|
|
|
**/
|
|
|
-proto.getCurrent = function getCurrent() {
|
|
|
+proto.getNext = function getNext(callback) {
|
|
|
throw new Error("not implemented");
|
|
|
};
|
|
|
|
|
|
@@ -136,10 +115,9 @@ proto.getSourceName = function getSourceName() {
|
|
|
* @method setSource
|
|
|
* @param {DocumentSource} source the underlying source to use
|
|
|
**/
|
|
|
-proto.setSource = function setSource(theSource, callback) {
|
|
|
+proto.setSource = function setSource(theSource) {
|
|
|
if (this.source) throw new Error("It is an error to set the source more than once");
|
|
|
this.source = theSource;
|
|
|
- if (callback) return setTimeout(callback, 0);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -218,42 +196,117 @@ klass.depsToProjection = function depsToProjection(deps) {
|
|
|
return bb;
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * Add the DocumentSource to the array builder.
|
|
|
- * The default implementation calls sourceToJson() in order to
|
|
|
- * convert the inner part of the object which will be added to the
|
|
|
- * array being built here.
|
|
|
- *
|
|
|
- * @method addToJsonArray
|
|
|
- * @param {Array} pBuilder JSONArrayBuilder: the array builder to add the operation to.
|
|
|
- * @param {Boolean} explain create explain output
|
|
|
- * @returns {Object}
|
|
|
- **/
|
|
|
-proto.addToJsonArray = function addToJsonArray(pBuilder, explain) {
|
|
|
- pBuilder.push(this.sourceToJson({}, explain));
|
|
|
+proto._serialize = function _serialize(explain) {
|
|
|
+ throw new Error("not implemented");
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * Create an object that represents the document source. The object
|
|
|
- * will have a single field whose name is the source's name. This
|
|
|
- * will be used by the default implementation of addToJsonArray()
|
|
|
- * to add this object to a pipeline being represented in JSON.
|
|
|
- *
|
|
|
- * @method sourceToJson
|
|
|
- * @param {Object} pBuilder JSONObjBuilder: a blank object builder to write to
|
|
|
- * @param {Boolean} explain create explain output
|
|
|
- **/
|
|
|
-proto.sourceToJson = function sourceToJson(pBuilder, explain) {
|
|
|
- throw new Error("not implemented");
|
|
|
+proto.serializeToArray = function serializeToArray(array, explain) {
|
|
|
+ var entry = this.serialize(explain);
|
|
|
+ if (!entry) {
|
|
|
+ array.push(entry);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * Convert the DocumentSource instance to it's JSON Object representation; Used by the standard JSON.stringify() function
|
|
|
- * @method toJSON
|
|
|
- * @return {String} a JSON-encoded String that represents the DocumentSource
|
|
|
- **/
|
|
|
-proto.toJSON = function toJSON(){
|
|
|
- var obj = {};
|
|
|
- this.sourceToJson(obj);
|
|
|
- return obj;
|
|
|
+proto.depsToProjection = function depsToProjection(deps) {
|
|
|
+ var needId = false,
|
|
|
+ last,
|
|
|
+ bb = {};
|
|
|
+
|
|
|
+ for (var i = 0; i < deps.length; i++) {
|
|
|
+ var it = deps[i];
|
|
|
+ if (it.starsWith('_id') && (it.length === 3 || it[3] === '.')) {
|
|
|
+ needId = true;
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ if (!last && it.starsWith(last)) {
|
|
|
+ // we are including a parent of *it so we don't need to include this field
|
|
|
+ // explicitly. In fact, due to SERVER-6527 if we included this field, the parent
|
|
|
+ // wouldn't be fully included. This logic relies on on set iterators going in
|
|
|
+ // lexicographic order so that a string is always directly before of all fields it
|
|
|
+ // prefixes.
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ last = it + '.';
|
|
|
+ bb[it] = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (needId) // we are explicit either way
|
|
|
+ bb._id = 1;
|
|
|
+ else
|
|
|
+ bb._id = 0;
|
|
|
+
|
|
|
+ return JSON.stringy(bb);
|
|
|
+};
|
|
|
+
|
|
|
+// Taken as a whole, these three functions should produce the same output document given the
|
|
|
+// same deps set as mongo::Projection::transform would on the output of depsToProjection. The
|
|
|
+// only exceptions are that we correctly handle the case where no fields are needed and we don't
|
|
|
+// need to work around the above mentioned bug with subfields of _id (SERVER-7502). This is
|
|
|
+// tested in a DEV block in DocumentSourceCursor::findNext().
|
|
|
+//
|
|
|
+// Output from this function is input for the next two
|
|
|
+//
|
|
|
+// ParsedDeps is a simple recursive look-up table. For each field in a ParsedDeps:
|
|
|
+// If the value has type==Bool, the whole field is needed
|
|
|
+// If the value has type==Object, the fields in the subobject are needed
|
|
|
+// All other fields should be missing which means not needed
|
|
|
+// DocumentSource::ParsedDeps DocumentSource::parseDeps(const set<string>& deps) {
|
|
|
+// MutableDocument md;
|
|
|
+proto.parseDeps = function parseDeps(deps) {
|
|
|
+ var doc,
|
|
|
+ last;
|
|
|
+
|
|
|
+ for (var i = 0; i < deps.length; i++) {
|
|
|
+ var it = deps[i];
|
|
|
+ if (!last && it.startsWith(last)) {
|
|
|
+ // we are including a parent of *it so we don't need to include this field
|
|
|
+ // explicitly. In fact, if we included this field, the parent wouldn't be fully
|
|
|
+ // included. This logic relies on on set iterators going in lexicographic order so
|
|
|
+ // that a string is always directly before of all fields it prefixes.
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ last = it + '.';
|
|
|
+ Object.setAtPath(doc, it, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ return doc;
|
|
|
+};
|
|
|
+
|
|
|
+proto.documentFromBsonWithDeps = function documentFromBsonWithDeps(obj, deps) {
|
|
|
+ var doc = {},
|
|
|
+ self = this;
|
|
|
+
|
|
|
+ var arrayHelper = function(field, isNeeded) {
|
|
|
+ return field.map(function(f) {
|
|
|
+ self.documentFromBsonWithDeps(f, isNeeded);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ for (var i = 0; i < obj.keys().length; i++) {
|
|
|
+ var fieldName = obj.keys()[i],
|
|
|
+ field = obj[fieldName],
|
|
|
+ isNeeded = deps[fieldName];
|
|
|
+
|
|
|
+ if (!isNeeded)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (typeof isNeeded === Boolean) {
|
|
|
+ Object.setAtPath(doc, fieldName, field);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(isNeeded instanceof Object))
|
|
|
+ throw new Error("dependencies missing for object");
|
|
|
+
|
|
|
+ if (field instanceof Array)
|
|
|
+ Object.setAtPath(doc, fieldName, arrayHelper(field, isNeeded));
|
|
|
+
|
|
|
+ if (field instanceof Object) { // everything is...
|
|
|
+ var sub = this.documentFromBsonWithDeps(field, isNeeded);
|
|
|
+ Object.setAtPath(doc, fieldName, sub);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return doc;
|
|
|
};
|