Browse Source

Refs #5121: Update DocumentSource

Chris Sexton 12 years ago
parent
commit
70982ba61e
1 changed files with 112 additions and 59 deletions
  1. 112 59
      lib/pipeline/documentSources/DocumentSource.js

+ 112 - 59
lib/pipeline/documentSources/DocumentSource.js

@@ -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;
 };