summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2013-12-20 14:57:18 -0500
committerMathias Stearn <mathias@10gen.com>2014-01-21 12:55:49 -0500
commitd0037946dc103ffa648f7e8937f2c55351b03c53 (patch)
tree4b249ff3088fe64a234754b513e771f7acdaa8b6
parent8bab6b0c9fad64286c22d928bbe8ebfae7e8b2c4 (diff)
downloadmongo-d0037946dc103ffa648f7e8937f2c55351b03c53.tar.gz
SERVER-12180 Clean up dependency tracking code
Behavior changes (none effect semantics of pipeline, just implementation): * We now track what we know about fields and metadata separately. This allows us to prove that we won't need the text score if we see a $group or $out. * If we don't need any fields from the source document we now use a projection that will return an empty document, or just the text score if it is needed. We used to use {_id: 0} which returned all other fields. Code organization changes: * Dependencies are now tracked using a dedicated struct rather than a set<string> with some magic strings. * ParsedDeps is now a proper class rather than a typedef. * Removed ExpressionFieldPath::_baseVar since its former role is fulfilled better by _variable.
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/db/pipeline/dependencies.cpp175
-rw-r--r--src/mongo/db/pipeline/dependencies.h77
-rw-r--r--src/mongo/db/pipeline/document_source.cpp138
-rw-r--r--src/mongo/db/pipeline/document_source.h54
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_group.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_out.cpp5
-rw-r--r--src/mongo/db/pipeline/document_source_project.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_sort.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_unwind.cpp4
-rw-r--r--src/mongo/db/pipeline/expression.cpp32
-rw-r--r--src/mongo/db/pipeline/expression.h27
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp84
-rw-r--r--src/mongo/dbtests/documentsourcetests.cpp135
-rw-r--r--src/mongo/dbtests/expressiontests.cpp54
16 files changed, 481 insertions, 331 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 6b5d99e5fa6..78e41bbf7c9 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -417,6 +417,7 @@ env.Library("coredb", [
"db/pipeline/accumulator_min_max.cpp",
"db/pipeline/accumulator_push.cpp",
"db/pipeline/accumulator_sum.cpp",
+ "db/pipeline/dependencies.cpp",
"db/pipeline/document.cpp",
"db/pipeline/document_source.cpp",
"db/pipeline/document_source_bson_array.cpp",
diff --git a/src/mongo/db/pipeline/dependencies.cpp b/src/mongo/db/pipeline/dependencies.cpp
new file mode 100644
index 00000000000..bed2332549d
--- /dev/null
+++ b/src/mongo/db/pipeline/dependencies.cpp
@@ -0,0 +1,175 @@
+/**
+* Copyright (C) 2014 MongoDB Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* As a special exception, the copyright holders give permission to link the
+* code of portions of this program with the OpenSSL library under certain
+* conditions as described in each individual source file and distribute
+* linked combinations including the program with the OpenSSL library. You
+* must comply with the GNU Affero General Public License in all respects for
+* all of the code used other than as permitted herein. If you modify file(s)
+* with this exception, you may extend this exception to your version of the
+* file(s), but you are not obligated to do so. If you do not wish to do so,
+* delete this exception statement from your version. If you delete this
+* exception statement from all source files in the program, then also delete
+* it in the license file.
+*/
+
+#include "mongo/pch.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/pipeline/dependencies.h"
+#include "mongo/db/pipeline/field_path.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+ namespace str = mongoutils::str;
+
+ BSONObj DepsTracker::toProjection() const {
+ BSONObjBuilder bb;
+
+ if (needTextScore)
+ bb.append(Document::metaFieldTextScore, BSON("$meta" << "textScore"));
+
+ if (needWholeDocument)
+ return bb.obj();
+
+ if (fields.empty()) {
+ // Projection language lacks good a way to say no fields needed. This fakes it.
+ bb.append("_id", 0);
+ bb.append("$noFieldsNeeded", 1);
+ return bb.obj();
+ }
+
+ bool needId = false;
+ string last;
+ for (set<string>::const_iterator it(fields.begin()), end(fields.end()); it!=end; ++it) {
+ if (str::startsWith(*it, "_id") && (it->size() == 3 || (*it)[3] == '.')) {
+ // _id and subfields are handled specially due in part to SERVER-7502
+ needId = true;
+ continue;
+ }
+
+ if (!last.empty() && str::startsWith(*it, 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.append(*it, 1);
+ }
+
+ if (needId) // we are explicit either way
+ bb.append("_id", 1);
+ else
+ bb.append("_id", 0);
+
+ return bb.obj();
+ }
+
+ // ParsedDeps::_fields is a simple recursive look-up table. For each field:
+ // 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
+ boost::optional<ParsedDeps> DepsTracker::toParsedDeps() const {
+ MutableDocument md;
+
+ if (needWholeDocument || needTextScore) {
+ // can't use ParsedDeps in this case
+ return boost::none;
+ }
+
+ string last;
+ for (set<string>::const_iterator it(fields.begin()), end(fields.end()); it!=end; ++it) {
+ if (!last.empty() && str::startsWith(*it, 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 + '.';
+ md.setNestedField(*it, Value(true));
+ }
+
+ return ParsedDeps(md.freeze());
+ }
+
+namespace {
+ // Mutually recursive with arrayHelper
+ Document documentHelper(const BSONObj& bson, const Document& neededFields);
+
+ // Handles array-typed values for ParsedDeps::extractFields
+ Value arrayHelper(const BSONObj& bson, const Document& neededFields) {
+ BSONObjIterator it(bson);
+
+ vector<Value> values;
+ while (it.more()) {
+ BSONElement bsonElement(it.next());
+ if (bsonElement.type() == Object) {
+ Document sub = documentHelper(bsonElement.embeddedObject(), neededFields);
+ values.push_back(Value(sub));
+ }
+
+ if (bsonElement.type() == Array) {
+ values.push_back(arrayHelper(bsonElement.embeddedObject(), neededFields));
+ }
+ }
+
+ return Value::consume(values);
+ }
+
+ // Handles object-typed values including the top-level for ParsedDeps::extractFields
+ Document documentHelper(const BSONObj& bson, const Document& neededFields) {
+ MutableDocument md(neededFields.size());
+
+ BSONObjIterator it(bson);
+ while (it.more()) {
+ BSONElement bsonElement (it.next());
+ StringData fieldName = bsonElement.fieldNameStringData();
+ Value isNeeded = neededFields[fieldName];
+
+ if (isNeeded.missing())
+ continue;
+
+ if (isNeeded.getType() == Bool) {
+ md.addField(fieldName, Value(bsonElement));
+ continue;
+ }
+
+ dassert(isNeeded.getType() == Object);
+
+ if (bsonElement.type() == Object) {
+ Document sub = documentHelper(bsonElement.embeddedObject(), isNeeded.getDocument());
+ md.addField(fieldName, Value(sub));
+ }
+
+ if (bsonElement.type() == Array) {
+ md.addField(fieldName, arrayHelper(bsonElement.embeddedObject(),
+ isNeeded.getDocument()));
+ }
+ }
+
+ return md.freeze();
+ }
+} // namespace
+
+ Document ParsedDeps::extractFields(const BSONObj& input) const {
+ return documentHelper(input, _fields);
+ }
+}
diff --git a/src/mongo/db/pipeline/dependencies.h b/src/mongo/db/pipeline/dependencies.h
new file mode 100644
index 00000000000..47f8f46c432
--- /dev/null
+++ b/src/mongo/db/pipeline/dependencies.h
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <set>
+#include <string>
+
+#include "mongo/db/pipeline/document.h"
+
+namespace mongo {
+ class ParsedDeps;
+
+ /**
+ * This struct allows components in an agg pipeline to report what they need from their input.
+ */
+ struct DepsTracker {
+ DepsTracker()
+ : needWholeDocument(false)
+ , needTextScore(false)
+ {}
+
+ /**
+ * Returns a projection object covering the dependencies tracked by this class.
+ */
+ BSONObj toProjection() const;
+
+ boost::optional<ParsedDeps> toParsedDeps() const;
+
+ std::set<std::string> fields; // names of needed fields in dotted notation
+ bool needWholeDocument; // if true, ignore fields and assume the whole document is needed
+ bool needTextScore;
+ };
+
+ /**
+ * This class is designed to quickly extract the needed fields from a BSONObj into a Document.
+ * It should only be created by a call to DepsTracker::ParsedDeps
+ */
+ class ParsedDeps {
+ public:
+ Document extractFields(const BSONObj& input) const;
+
+ private:
+ friend struct DepsTracker; // so it can call constructor
+ explicit ParsedDeps(const Document& fields)
+ : _fields(fields)
+ {}
+
+ Document _fields;
+ };
+}
diff --git a/src/mongo/db/pipeline/document_source.cpp b/src/mongo/db/pipeline/document_source.cpp
index f07739479e3..d4c21fca2fd 100644
--- a/src/mongo/db/pipeline/document_source.cpp
+++ b/src/mongo/db/pipeline/document_source.cpp
@@ -59,8 +59,6 @@ namespace mongo {
void DocumentSource::dispose() {
if ( pSource ) {
- // This is required for the DocumentSourceCursor to release its read lock, see
- // SERVER-6123.
pSource->dispose();
}
}
@@ -71,140 +69,4 @@ namespace mongo {
array.push_back(entry);
}
}
-
- BSONObj DocumentSource::depsToProjection(const set<string>& deps) {
- BSONObjBuilder bb;
-
- bool needId = false;
-
- string last;
- for (set<string>::const_iterator it(deps.begin()), end(deps.end()); it!=end; ++it) {
- if (str::startsWith(*it, "_id") && (it->size() == 3 || (*it)[3] == '.')) {
- // _id and subfields are handled specially due in part to SERVER-7502
- needId = true;
- continue;
- }
-
- if (str::startsWith(*it, '$')) {
- if (*it == "$textScore") {
- // textScore metadata
- bb.append(Document::metaFieldTextScore, BSON("$meta" << "textScore"));
- continue;
- }
- }
-
- if (!last.empty() && str::startsWith(*it, 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.append(*it, 1);
- }
-
- if (needId) // we are explicit either way
- bb.append("_id", 1);
- else
- bb.append("_id", 0);
-
- return bb.obj();
- }
-
- // 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;
-
- string last;
- for (set<string>::const_iterator it(deps.begin()), end(deps.end()); it!=end; ++it) {
- if (str::startsWith(*it, '$')) {
- // documentFromBsonWithDeps doesn't handle meta data
- if (*it == "$textScore")
- return ParsedDeps();
- }
-
- if (!last.empty() && str::startsWith(*it, 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 + '.';
- md.setNestedField(*it, Value(true));
- }
-
- return md.freeze();
- }
-
- // Helper for next function
- static Value arrayHelper(const BSONObj& bson, const DocumentSource::ParsedDeps& neededFields) {
- BSONObjIterator it(bson);
-
- vector<Value> values;
- while (it.more()) {
- BSONElement bsonElement(it.next());
- if (bsonElement.type() == Object) {
- Document sub = DocumentSource::documentFromBsonWithDeps(
- bsonElement.embeddedObject(),
- neededFields);
- values.push_back(Value(sub));
- }
-
- if (bsonElement.type() == Array) {
- values.push_back(arrayHelper(bsonElement.embeddedObject(), neededFields));
- }
- }
-
- return Value::consume(values);
- }
-
- Document DocumentSource::documentFromBsonWithDeps(const BSONObj& bson,
- const ParsedDeps& neededFields) {
- MutableDocument md(neededFields.size());
-
- BSONObjIterator it(bson);
- while (it.more()) {
- BSONElement bsonElement (it.next());
- StringData fieldName = bsonElement.fieldNameStringData();
- Value isNeeded = neededFields[fieldName];
-
- if (isNeeded.missing())
- continue;
-
- if (isNeeded.getType() == Bool) {
- md.addField(fieldName, Value(bsonElement));
- continue;
- }
-
- dassert(isNeeded.getType() == Object);
-
- if (bsonElement.type() == Object) {
- Document sub = documentFromBsonWithDeps(bsonElement.embeddedObject(),
- isNeeded.getDocument());
- md.addField(fieldName, Value(sub));
- }
-
- if (bsonElement.type() == Array) {
- md.addField(fieldName, arrayHelper(bsonElement.embeddedObject(),
- isNeeded.getDocument()));
- }
- }
-
- return md.freeze();
- }
}
diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h
index 604b3117a16..a6bd0e8727b 100644
--- a/src/mongo/db/pipeline/document_source.h
+++ b/src/mongo/db/pipeline/document_source.h
@@ -38,6 +38,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher.h"
#include "mongo/db/pipeline/document.h"
+#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/value.h"
@@ -128,33 +129,20 @@ namespace mongo {
virtual void optimize();
enum GetDepsReturn {
- NOT_SUPPORTED, // This means the set should be ignored and the full object is required.
- EXHAUSTIVE, // This means that everything needed should be in the set
- SEE_NEXT, // Add the next Source's deps to the set
+ NOT_SUPPORTED = 0x0, // The full object and all metadata may be required
+ SEE_NEXT = 0x1, // Later stages could need either fields or metadata
+ EXHAUSTIVE_FIELDS = 0x2, // Later stages won't need more fields from input
+ EXHAUSTIVE_META = 0x4, // Later stages won't need more metadata from input
+ EXHAUSTIVE_ALL = EXHAUSTIVE_FIELDS | EXHAUSTIVE_META, // Later stages won't need either
};
- /** Get the fields this operation needs to do its job.
- * Deps should be in "a.b.c" notation
- * An empty string in deps means the whole document is needed.
- *
- * @param deps results are added here. NOT CLEARED
+ /**
+ * Get the dependencies this operation needs to do its job.
*/
- virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const {
return NOT_SUPPORTED;
}
- /** This takes dependencies from getDependencies and
- * returns a projection that includes all of them
- */
- static BSONObj depsToProjection(const set<string>& deps);
-
- /** These functions take the same input as depsToProjection but are able to
- * produce a Document from a BSONObj with the needed fields much faster.
- */
- typedef Document ParsedDeps; // See implementation for structure
- static ParsedDeps parseDeps(const set<string>& deps);
- static Document documentFromBsonWithDeps(const BSONObj& object, const ParsedDeps& deps);
-
/**
* In the default case, serializes the DocumentSource and adds it to the vector<Value>.
*
@@ -408,12 +396,9 @@ namespace mongo {
*
* @param projection A projection specification describing the fields needed by the rest of
* the pipeline.
- * @param deps The output of DocumentSource::parseDeps.
- * @param projectionInQuery True if the underlying cursor will handle the projection for us.
+ * @param deps The output of DepsTracker::toParsedDeps
*/
- void setProjection(const BSONObj& projection,
- const ParsedDeps& deps,
- bool projectionInQuery);
+ void setProjection(const BSONObj& projection, const boost::optional<ParsedDeps>& deps);
/// returns -1 for no limit
long long getLimit() const;
@@ -432,9 +417,7 @@ namespace mongo {
BSONObj _query;
BSONObj _sort;
BSONObj _projection;
- bool _haveDeps;
- ParsedDeps _dependencies;
- bool _projectionInQuery;
+ boost::optional<ParsedDeps> _dependencies;
intrusive_ptr<DocumentSourceLimit> _limit;
long long _docsAddedToBatches; // for _limit enforcement
@@ -450,7 +433,7 @@ namespace mongo {
virtual boost::optional<Document> getNext();
virtual const char *getSourceName() const;
virtual void optimize();
- virtual GetDepsReturn getDependencies(set<string>& deps) const;
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const;
virtual void dispose();
virtual Value serialize(bool explain = false) const;
@@ -687,6 +670,7 @@ namespace mongo {
virtual boost::optional<Document> getNext();
virtual const char *getSourceName() const;
virtual Value serialize(bool explain = false) const;
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const;
// Virtuals for SplittableDocumentSource
virtual intrusive_ptr<DocumentSource> getShardSource() { return NULL; }
@@ -735,7 +719,7 @@ namespace mongo {
virtual void optimize();
virtual Value serialize(bool explain = false) const;
- virtual GetDepsReturn getDependencies(set<string>& deps) const;
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const;
/**
Create a new projection DocumentSource from BSON.
@@ -809,7 +793,7 @@ namespace mongo {
virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
virtual void dispose();
- virtual GetDepsReturn getDependencies(set<string>& deps) const;
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const;
virtual intrusive_ptr<DocumentSource> getShardSource();
virtual intrusive_ptr<DocumentSource> getMergeSource();
@@ -923,7 +907,7 @@ namespace mongo {
virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
virtual Value serialize(bool explain = false) const;
- virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const {
return SEE_NEXT; // This doesn't affect needed fields
}
@@ -979,7 +963,7 @@ namespace mongo {
virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
virtual Value serialize(bool explain = false) const;
- virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const {
return SEE_NEXT; // This doesn't affect needed fields
}
@@ -1033,7 +1017,7 @@ namespace mongo {
virtual const char *getSourceName() const;
virtual Value serialize(bool explain = false) const;
- virtual GetDepsReturn getDependencies(set<string>& deps) const;
+ virtual GetDepsReturn getDependencies(DepsTracker* deps) const;
/**
Create a new projection DocumentSource from BSON.
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
index bdc76a1f516..6be57a990d3 100644
--- a/src/mongo/db/pipeline/document_source_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -97,8 +97,8 @@ namespace mongo {
BSONObj obj;
Runner::RunnerState state;
while ((state = runner->getNext(&obj, NULL)) == Runner::RUNNER_ADVANCED) {
- if (_haveDeps && !_projectionInQuery) {
- _currentBatch.push_back(documentFromBsonWithDeps(obj, _dependencies));
+ if (_dependencies) {
+ _currentBatch.push_back(_dependencies->extractFields(obj));
}
else {
_currentBatch.push_back(Document::fromBsonWithMetaData(obj));
@@ -255,7 +255,6 @@ namespace {
CursorId cursorId,
const intrusive_ptr<ExpressionContext> &pCtx)
: DocumentSource(pCtx)
- , _haveDeps(false)
, _docsAddedToBatches(0)
, _ns(ns)
, _cursorId(cursorId)
@@ -270,11 +269,8 @@ namespace {
void DocumentSourceCursor::setProjection(
const BSONObj& projection,
- const ParsedDeps& deps,
- bool projectionInQuery) {
+ const boost::optional<ParsedDeps>& deps) {
_projection = projection;
_dependencies = deps;
- _projectionInQuery = projectionInQuery;
- _haveDeps = true;
}
}
diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp
index 7e7e0bb9e2b..b5350f61fa5 100644
--- a/src/mongo/db/pipeline/document_source_group.cpp
+++ b/src/mongo/db/pipeline/document_source_group.cpp
@@ -153,7 +153,7 @@ namespace mongo {
return Value(DOC(getSourceName() << insides.freeze()));
}
- DocumentSource::GetDepsReturn DocumentSourceGroup::getDependencies(set<string>& deps) const {
+ DocumentSource::GetDepsReturn DocumentSourceGroup::getDependencies(DepsTracker* deps) const {
// add the _id
pIdExpression->addDependencies(deps);
@@ -163,7 +163,7 @@ namespace mongo {
vpExpression[i]->addDependencies(deps);
}
- return EXHAUSTIVE;
+ return EXHAUSTIVE_ALL;
}
intrusive_ptr<DocumentSourceGroup> DocumentSourceGroup::create(
diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp
index b347e52d3e2..2fc597d7e9c 100644
--- a/src/mongo/db/pipeline/document_source_out.cpp
+++ b/src/mongo/db/pipeline/document_source_out.cpp
@@ -180,4 +180,9 @@ namespace mongo {
return Value(DOC(getSourceName() << _outputNs.coll()));
}
+
+ DocumentSource::GetDepsReturn DocumentSourceOut::getDependencies(DepsTracker* deps) const {
+ deps->needWholeDocument = true;
+ return EXHAUSTIVE_ALL;
+ }
}
diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp
index c6708524f45..8ef3e1c6433 100644
--- a/src/mongo/db/pipeline/document_source_project.cpp
+++ b/src/mongo/db/pipeline/document_source_project.cpp
@@ -131,19 +131,19 @@ namespace mongo {
#if defined(_DEBUG)
if (exprObj->isSimple()) {
- set<string> deps;
+ DepsTracker deps;
vector<string> path;
- exprObj->addDependencies(deps, &path);
- pProject->_simpleProjection.init(depsToProjection(deps));
+ exprObj->addDependencies(&deps, &path);
+ pProject->_simpleProjection.init(deps.toProjection());
}
#endif
return pProject;
}
- DocumentSource::GetDepsReturn DocumentSourceProject::getDependencies(set<string>& deps) const {
+ DocumentSource::GetDepsReturn DocumentSourceProject::getDependencies(DepsTracker* deps) const {
vector<string> path; // empty == top-level
pEO->addDependencies(deps, &path);
- return EXHAUSTIVE;
+ return EXHAUSTIVE_FIELDS;
}
}
diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp
index c75feb629ce..f25e5fd65e5 100644
--- a/src/mongo/db/pipeline/document_source_sort.cpp
+++ b/src/mongo/db/pipeline/document_source_sort.cpp
@@ -129,7 +129,7 @@ namespace mongo {
return keyObj.freeze();
}
- DocumentSource::GetDepsReturn DocumentSourceSort::getDependencies(set<string>& deps) const {
+ DocumentSource::GetDepsReturn DocumentSourceSort::getDependencies(DepsTracker* deps) const {
for(size_t i = 0; i < vSortKey.size(); ++i) {
vSortKey[i]->addDependencies(deps);
}
diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp
index 890214c4118..f7c28fa8ec4 100644
--- a/src/mongo/db/pipeline/document_source_unwind.cpp
+++ b/src/mongo/db/pipeline/document_source_unwind.cpp
@@ -143,8 +143,8 @@ namespace mongo {
return Value(DOC(getSourceName() << _unwindPath->getPath(true)));
}
- DocumentSource::GetDepsReturn DocumentSourceUnwind::getDependencies(set<string>& deps) const {
- deps.insert(_unwindPath->getPath(false));
+ DocumentSource::GetDepsReturn DocumentSourceUnwind::getDependencies(DepsTracker* deps) const {
+ deps->fields.insert(_unwindPath->getPath(false));
return SEE_NEXT;
}
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 449360ec36c..82d5c69cbea 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -587,7 +587,7 @@ namespace {
return intrusive_ptr<Expression>(this);
}
- void ExpressionCoerceToBool::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionCoerceToBool::addDependencies(DepsTracker* deps, vector<string>* path) const {
pExpression->addDependencies(deps);
}
@@ -782,7 +782,7 @@ namespace {
return intrusive_ptr<Expression>(this);
}
- void ExpressionConstant::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionConstant::addDependencies(DepsTracker* deps, vector<string>* path) const {
/* nothing to do */
}
@@ -901,13 +901,13 @@ namespace {
return true;
}
- void ExpressionObject::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionObject::addDependencies(DepsTracker* deps, vector<string>* path) const {
string pathStr;
if (path) {
if (path->empty()) {
// we are in the top level of a projection so _id is implicit
if (!_excludeId)
- deps.insert("_id");
+ deps->fields.insert("_id");
}
else {
FieldPath f (*path);
@@ -930,7 +930,7 @@ namespace {
uassert(16407, "inclusion not supported in objects nested in $expressions",
path);
- deps.insert(pathStr + it->first);
+ deps->fields.insert(pathStr + it->first);
}
}
}
@@ -1201,9 +1201,6 @@ namespace {
ExpressionFieldPath::ExpressionFieldPath(const string& theFieldPath, Variables::Id variable)
: _fieldPath(theFieldPath)
, _variable(variable)
- , _baseVar(_fieldPath.getFieldName(0) == "CURRENT" ? CURRENT :
- _fieldPath.getFieldName(0) == "ROOT" ? ROOT :
- OTHER)
{}
intrusive_ptr<Expression> ExpressionFieldPath::optimize() {
@@ -1211,13 +1208,12 @@ namespace {
return intrusive_ptr<Expression>(this);
}
- void ExpressionFieldPath::addDependencies(set<string>& deps, vector<string>* path) const {
- // TODO consider state of variables
- if (_baseVar == ROOT || _baseVar == CURRENT) {
+ void ExpressionFieldPath::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ if (_variable == Variables::ROOT_ID) { // includes CURRENT when it is equivalent to ROOT.
if (_fieldPath.getPathLength() == 1) {
- deps.insert(""); // need full doc if just "$$ROOT" or "$$CURRENT"
+ deps->needWholeDocument = true; // need full doc if just "$$ROOT"
} else {
- deps.insert(_fieldPath.tail().getPath(false));
+ deps->fields.insert(_fieldPath.tail().getPath(false));
}
}
}
@@ -1383,7 +1379,7 @@ namespace {
return _subExpression->evaluateInternal(vars);
}
- void ExpressionLet::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionLet::addDependencies(DepsTracker* deps, vector<string>* path) const {
for (VariableMap::const_iterator it=_variables.begin(), end=_variables.end();
it != end; ++it) {
it->second.expression->addDependencies(deps);
@@ -1500,7 +1496,7 @@ namespace {
return Value::consume(output);
}
- void ExpressionMap::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionMap::addDependencies(DepsTracker* deps, vector<string>* path) const {
_input->addDependencies(deps);
_each->addDependencies(deps);
}
@@ -1531,8 +1527,8 @@ namespace {
: Value();
}
- void ExpressionMeta::addDependencies(set<string>& deps, vector<string>* path) const {
- deps.insert("$textScore");
+ void ExpressionMeta::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ deps->needTextScore = true;
}
/* ------------------------- ExpressionMillisecond ----------------------------- */
@@ -1782,7 +1778,7 @@ namespace {
return this;
}
- void ExpressionNary::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionNary::addDependencies(DepsTracker* deps, vector<string>* path) const {
for(ExpressionVector::const_iterator i(vpOperand.begin());
i != vpOperand.end(); ++i) {
(*i)->addDependencies(deps);
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 99121ed1d55..31a0171ecc2 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -30,6 +30,7 @@
#include "mongo/pch.h"
+#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/value.h"
@@ -176,7 +177,7 @@ namespace mongo {
* where {a:1} inclusion objects aren't allowed, they get
* NULL.
*/
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const = 0;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const = 0;
/** simple expressions are just inclusion exclusion as supported by ExpressionObject */
virtual bool isSimple() { return false; }
@@ -292,7 +293,7 @@ namespace mongo {
// virtuals from Expression
virtual intrusive_ptr<Expression> optimize();
virtual Value serialize(bool explain) const;
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
/*
Add an operand to the n-ary expression.
@@ -398,7 +399,7 @@ namespace mongo {
public:
// virtuals from ExpressionNary
virtual intrusive_ptr<Expression> optimize();
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
virtual Value evaluateInternal(Variables* vars) const;
virtual Value serialize(bool explain) const;
@@ -470,7 +471,7 @@ namespace mongo {
public:
// virtuals from Expression
virtual intrusive_ptr<Expression> optimize();
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
virtual Value evaluateInternal(Variables* vars) const;
virtual const char *getOpName() const;
virtual Value serialize(bool explain) const;
@@ -530,7 +531,7 @@ namespace mongo {
public:
// virtuals from Expression
virtual intrusive_ptr<Expression> optimize();
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
virtual Value evaluateInternal(Variables* vars) const;
virtual Value serialize(bool explain) const;
@@ -577,16 +578,8 @@ namespace mongo {
// Helper for evaluatePath to handle Array case
Value evaluatePathArray(size_t index, const Value& input) const;
- // A cache of string comparison of _fieldPath.getFieldName(0)
- enum BaseVar {
- CURRENT,
- ROOT,
- OTHER,
- };
-
const FieldPath _fieldPath;
const Variables::Id _variable;
- const BaseVar _baseVar; // TODO remove
};
@@ -612,7 +605,7 @@ namespace mongo {
virtual intrusive_ptr<Expression> optimize();
virtual Value serialize(bool explain) const;
virtual Value evaluateInternal(Variables* vars) const;
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
static intrusive_ptr<Expression> parse(
BSONElement expr,
@@ -645,7 +638,7 @@ namespace mongo {
virtual intrusive_ptr<Expression> optimize();
virtual Value serialize(bool explain) const;
virtual Value evaluateInternal(Variables* vars) const;
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
static intrusive_ptr<Expression> parse(
BSONElement expr,
@@ -668,7 +661,7 @@ namespace mongo {
// virtuals from Expression
virtual Value serialize(bool explain) const;
virtual Value evaluateInternal(Variables* vars) const;
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
static intrusive_ptr<Expression> parse(
BSONElement expr,
@@ -729,7 +722,7 @@ namespace mongo {
// virtuals from Expression
virtual intrusive_ptr<Expression> optimize();
virtual bool isSimple();
- virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual void addDependencies(DepsTracker* deps, vector<string>* path=NULL) const;
/** Only evaluates non inclusion expressions. For inclusions, use addToDocument(). */
virtual Value evaluateInternal(Variables* vars) const;
virtual Value serialize(bool explain) const;
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index 0578f2ac5aa..20fabe2a4e7 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -101,45 +101,51 @@ namespace {
}
- /* Look for an initial simple project; we'll avoid constructing Values
- * for fields that won't make it through the projection.
- */
-
- bool haveProjection = false;
- bool needQueryProjection = false; // true if we need to send the project to query system
- BSONObj projection;
- DocumentSource::ParsedDeps dependencies;
+
+ // Find the set of fields in the source documents depended on by this pipeline.
+ DepsTracker deps; // should be considered const after the following block.
{
- const bool isTextQuery = DocumentSourceMatch::isTextQuery(queryObj);
- needQueryProjection = isTextQuery;
-
- set<string> deps;
- DocumentSource::GetDepsReturn status = DocumentSource::SEE_NEXT;
- for (size_t i=0; i < sources.size() && status == DocumentSource::SEE_NEXT; i++) {
- status = sources[i]->getDependencies(deps);
- if (deps.count(string())) // empty string means we need the full doc
- status = DocumentSource::NOT_SUPPORTED;
- }
+ bool knowAllFields = false;
+ bool knowAllMeta = false;
+ for (size_t i=0; i < sources.size() && !(knowAllFields && knowAllMeta); i++) {
+ DepsTracker localDeps;
+ DocumentSource::GetDepsReturn status = sources[i]->getDependencies(&localDeps);
+
+ if (status == DocumentSource::NOT_SUPPORTED) {
+ // Assume this stage needs everything. We may still know something about our
+ // dependencies if an earlier stage returned either EXHAUSTIVE_FIELDS or
+ // EXHAUSTIVE_META.
+ break;
+ }
- // If doing a text query, assume we need score since we can't prove we don't.
- // Edge cases that make this tricky:
- // * Need to propagate score from shards to merger even if nothing on shard needs score.
- // * Stages that are EXHAUSTIVE for field dependencies still propagate metadata.
- if (isTextQuery)
- deps.insert("$textScore");
-
- if (status == DocumentSource::EXHAUSTIVE) {
- projection = DocumentSource::depsToProjection(deps);
- dependencies = DocumentSource::parseDeps(deps);
- haveProjection = true;
- }
- else if (isTextQuery) {
- // We still need score even if we don't know what actual fields are needed.
- projection = BSON(Document::metaFieldTextScore << BSON("$meta" << "textScore"));
- haveProjection = true;
+ if (!knowAllFields) {
+ deps.fields.insert(localDeps.fields.begin(), localDeps.fields.end());
+ if (localDeps.needWholeDocument)
+ deps.needWholeDocument = true;
+ knowAllFields = status & DocumentSource::EXHAUSTIVE_FIELDS;
+ }
+
+ if (!knowAllMeta) {
+ if (localDeps.needTextScore)
+ deps.needTextScore = true;
+
+ knowAllMeta = status & DocumentSource::EXHAUSTIVE_META;
+ }
}
+
+ if (!knowAllFields)
+ deps.needWholeDocument = true; // don't know all fields we need
+
+ // If doing a text query, assume we need the score if we can't prove we don't.
+ if (!knowAllMeta && DocumentSourceMatch::isTextQuery(queryObj))
+ deps.needTextScore = true;
}
+ // Passing query an empty projection since it is faster to use ParsedDeps::extractFields().
+ // This will need to change to support covering indexes (SERVER-12015). There is an
+ // exception for textScore since that can only be retrieved by a query projection.
+ const BSONObj projectionForQuery = deps.needTextScore ? deps.toProjection() : BSONObj();
+
/*
Look for an initial sort; we'll try to add this to the
Cursor we create. If we're successful in doing that (further down),
@@ -201,13 +207,11 @@ namespace {
bool sortInRunner = false;
if (sortStage) {
CanonicalQuery* cq;
- // Passing an empty projection since it is faster to use documentFromBsonWithDeps.
- // This will need to change to support covering indexes (SERVER-12015).
Status status =
CanonicalQuery::canonicalize(pExpCtx->ns,
queryObj,
sortObj,
- needQueryProjection ? projection : BSONObj(),
+ projectionForQuery,
&cq);
Runner* rawRunner;
if (status.isOK() && getRunner(cq, &rawRunner, runnerOptions).isOK()) {
@@ -230,7 +234,7 @@ namespace {
CanonicalQuery::canonicalize(pExpCtx->ns,
queryObj,
noSort,
- needQueryProjection ? projection : BSONObj(),
+ projectionForQuery,
&cq));
Runner* rawRunner;
@@ -258,9 +262,7 @@ namespace {
if (sortInRunner)
pSource->setSort(sortObj);
- if (haveProjection) {
- pSource->setProjection(projection, dependencies, needQueryProjection);
- }
+ pSource->setProjection(deps.toProjection(), deps.toParsedDeps());
while (!sources.empty() && pSource->coalesce(sources.front())) {
sources.pop_front();
diff --git a/src/mongo/dbtests/documentsourcetests.cpp b/src/mongo/dbtests/documentsourcetests.cpp
index 221fb13511d..48d13049d9e 100644
--- a/src/mongo/dbtests/documentsourcetests.cpp
+++ b/src/mongo/dbtests/documentsourcetests.cpp
@@ -33,6 +33,7 @@
#include <boost/thread/thread.hpp>
#include "mongo/db/interrupt_status_mongod.h"
+#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/document_source.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/query/get_runner.h"
@@ -42,6 +43,7 @@
namespace DocumentSourceTests {
static const char* const ns = "unittests.documentsourcetests";
+ static const BSONObj metaTextScore = BSON("$meta" << "textScore");
static DBDirectClient client;
BSONObj toBson( const intrusive_ptr<DocumentSource>& source ) {
@@ -74,38 +76,72 @@ namespace DocumentSourceTests {
void run() {
{
const char* array[] = {"a", "b"}; // basic
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "b" << 1 << "_id" << 0));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "b" << 1 << "_id" << 0));
}
{
const char* array[] = {"a", "ab"}; // prefixed but not subfield
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "ab" << 1 << "_id" << 0));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "ab" << 1 << "_id" << 0));
}
{
const char* array[] = {"a", "b", "a.b"}; // a.b included by a
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "b" << 1 << "_id" << 0));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "b" << 1 << "_id" << 0));
}
{
const char* array[] = {"a", "_id"}; // _id now included
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "_id" << 1));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "_id" << 1));
}
{
const char* array[] = {"a", "_id.a"}; // still include whole _id (SERVER-7502)
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "_id" << 1));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "_id" << 1));
}
{
const char* array[] = {"a", "_id", "_id.a"}; // handle both _id and subfield
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("a" << 1 << "_id" << 1));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("a" << 1 << "_id" << 1));
}
{
const char* array[] = {"a", "_id", "_id_a"}; // _id prefixed but non-subfield
- BSONObj proj = DocumentSource::depsToProjection(arrayToSet(array));
- ASSERT_EQUALS(proj, BSON("_id_a" << 1 << "a" << 1 << "_id" << 1));
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ ASSERT_EQUALS(deps.toProjection(), BSON("_id_a" << 1 << "a" << 1 << "_id" << 1));
+ }
+ {
+ const char* array[] = {"a"}; // fields ignored with needWholeDocument
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ deps.needWholeDocument = true;
+ ASSERT_EQUALS(deps.toProjection(), BSONObj());
+ }
+ {
+ const char* array[] = {"a"}; // needTextScore with needWholeDocument
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ deps.needWholeDocument = true;
+ deps.needTextScore = true;
+ ASSERT_EQUALS(
+ deps.toProjection(),
+ BSON(Document::metaFieldTextScore << metaTextScore));
+ }
+ {
+ const char* array[] = {"a"}; // needTextScore without needWholeDocument
+ DepsTracker deps;
+ deps.fields = arrayToSet(array);
+ deps.needTextScore = true;
+ ASSERT_EQUALS(deps.toProjection(),
+ BSON(Document::metaFieldTextScore << metaTextScore
+ << "a" << 1
+ << "_id" << 0));
}
}
};
@@ -408,9 +444,11 @@ namespace DocumentSourceTests {
public:
void run() {
createLimit( 1 );
- set<string> dependencies;
- ASSERT_EQUALS( DocumentSource::SEE_NEXT, limit()->getDependencies( dependencies ) );
- ASSERT_EQUALS( 0U, dependencies.size() );
+ DepsTracker dependencies;
+ ASSERT_EQUALS( DocumentSource::SEE_NEXT, limit()->getDependencies(&dependencies) );
+ ASSERT_EQUALS( 0U, dependencies.fields.size() );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -855,16 +893,18 @@ namespace DocumentSourceTests {
public:
void run() {
createGroup( fromjson( "{_id:'$x',a:{$sum:'$y.z'},b:{$avg:{$add:['$u','$v']}}}" ) );
- set<string> dependencies;
- ASSERT_EQUALS( DocumentSource::EXHAUSTIVE,
- group()->getDependencies( dependencies ) );
- ASSERT_EQUALS( 4U, dependencies.size() );
+ DepsTracker dependencies;
+ ASSERT_EQUALS( DocumentSource::EXHAUSTIVE_ALL,
+ group()->getDependencies( &dependencies ) );
+ ASSERT_EQUALS( 4U, dependencies.fields.size() );
// Dependency from _id expression.
- ASSERT_EQUALS( 1U, dependencies.count( "x" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "x" ) );
// Dependencies from accumulator expressions.
- ASSERT_EQUALS( 1U, dependencies.count( "y.z" ) );
- ASSERT_EQUALS( 1U, dependencies.count( "u" ) );
- ASSERT_EQUALS( 1U, dependencies.count( "v" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "y.z" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "u" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "v" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -1027,20 +1067,23 @@ namespace DocumentSourceTests {
class Dependencies : public Base {
public:
void run() {
- createProject( fromjson( "{a:true,x:'$b',y:{$and:['$c','$d']}}" ) );
- set<string> dependencies;
- ASSERT_EQUALS( DocumentSource::EXHAUSTIVE,
- project()->getDependencies( dependencies ) );
- ASSERT_EQUALS( 5U, dependencies.size() );
+ createProject(fromjson(
+ "{a:true,x:'$b',y:{$and:['$c','$d']}, z: {$meta:'textScore'}}"));
+ DepsTracker dependencies;
+ ASSERT_EQUALS( DocumentSource::EXHAUSTIVE_FIELDS,
+ project()->getDependencies( &dependencies ) );
+ ASSERT_EQUALS( 5U, dependencies.fields.size() );
// Implicit _id dependency.
- ASSERT_EQUALS( 1U, dependencies.count( "_id" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "_id" ) );
// Inclusion dependency.
- ASSERT_EQUALS( 1U, dependencies.count( "a" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "a" ) );
// Field path expression dependency.
- ASSERT_EQUALS( 1U, dependencies.count( "b" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "b" ) );
// Nested expression dependencies.
- ASSERT_EQUALS( 1U, dependencies.count( "c" ) );
- ASSERT_EQUALS( 1U, dependencies.count( "d" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "c" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "d" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( true, dependencies.needTextScore );
}
};
@@ -1368,11 +1411,13 @@ namespace DocumentSourceTests {
public:
void run() {
createSort( BSON( "a" << 1 << "b.c" << -1 ) );
- set<string> dependencies;
- ASSERT_EQUALS( DocumentSource::SEE_NEXT, sort()->getDependencies( dependencies ) );
- ASSERT_EQUALS( 2U, dependencies.size() );
- ASSERT_EQUALS( 1U, dependencies.count( "a" ) );
- ASSERT_EQUALS( 1U, dependencies.count( "b.c" ) );
+ DepsTracker dependencies;
+ ASSERT_EQUALS( DocumentSource::SEE_NEXT, sort()->getDependencies( &dependencies ) );
+ ASSERT_EQUALS( 2U, dependencies.fields.size() );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "a" ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "b.c" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -1629,11 +1674,13 @@ namespace DocumentSourceTests {
public:
void run() {
createUnwind( "$x.y.z" );
- set<string> dependencies;
+ DepsTracker dependencies;
ASSERT_EQUALS( DocumentSource::SEE_NEXT,
- unwind()->getDependencies( dependencies ) );
- ASSERT_EQUALS( 1U, dependencies.size() );
- ASSERT_EQUALS( 1U, dependencies.count( "x.y.z" ) );
+ unwind()->getDependencies( &dependencies ) );
+ ASSERT_EQUALS( 1U, dependencies.fields.size() );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "x.y.z" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
diff --git a/src/mongo/dbtests/expressiontests.cpp b/src/mongo/dbtests/expressiontests.cpp
index 3b2307855ff..ab4cf9c4479 100644
--- a/src/mongo/dbtests/expressiontests.cpp
+++ b/src/mongo/dbtests/expressiontests.cpp
@@ -521,10 +521,12 @@ namespace ExpressionTests {
void run() {
intrusive_ptr<Expression> nested = ExpressionFieldPath::create( "a.b" );
intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create( nested );
- set<string> dependencies;
- expression->addDependencies( dependencies );
- ASSERT_EQUALS( 1U, dependencies.size() );
- ASSERT_EQUALS( 1U, dependencies.count( "a.b" ) );
+ DepsTracker dependencies;
+ expression->addDependencies( &dependencies );
+ ASSERT_EQUALS( 1U, dependencies.fields.size() );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "a.b" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -924,9 +926,11 @@ namespace ExpressionTests {
void run() {
intrusive_ptr<Expression> expression =
ExpressionConstant::create( Value( 5 ) );
- set<string> dependencies;
- expression->addDependencies( dependencies );
- ASSERT_EQUALS( 0U, dependencies.size() );
+ DepsTracker dependencies;
+ expression->addDependencies( &dependencies );
+ ASSERT_EQUALS( 0U, dependencies.fields.size() );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -990,10 +994,12 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- set<string> dependencies;
- expression->addDependencies( dependencies );
- ASSERT_EQUALS( 1U, dependencies.size() );
- ASSERT_EQUALS( 1U, dependencies.count( "a.b" ) );
+ DepsTracker dependencies;
+ expression->addDependencies( &dependencies );
+ ASSERT_EQUALS( 1U, dependencies.fields.size() );
+ ASSERT_EQUALS( 1U, dependencies.fields.count( "a.b" ) );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -1282,14 +1288,17 @@ namespace ExpressionTests {
private:
void assertDependencies( const BSONArray& expectedDependencies,
const intrusive_ptr<Expression>& expression ) {
- set<string> dependencies;
- expression->addDependencies( dependencies );
+ DepsTracker dependencies;
+ expression->addDependencies( &dependencies );
BSONArrayBuilder dependenciesBson;
- for( set<string>::const_iterator i = dependencies.begin(); i != dependencies.end();
- ++i ) {
+ for( set<string>::const_iterator i = dependencies.fields.begin();
+ i != dependencies.fields.end();
+ ++i ) {
dependenciesBson << *i;
}
ASSERT_EQUALS( expectedDependencies, dependenciesBson.arr() );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -1450,15 +1459,18 @@ namespace ExpressionTests {
void assertDependencies( const BSONArray& expectedDependencies,
const intrusive_ptr<ExpressionObject>& expression,
bool includePath = true ) const {
- set<string> dependencies;
vector<string> path;
- expression->addDependencies( dependencies, includePath ? &path : 0 );
+ DepsTracker dependencies;
+ expression->addDependencies( &dependencies, includePath ? &path : 0 );
BSONArrayBuilder bab;
- for( set<string>::const_iterator i = dependencies.begin(); i != dependencies.end();
- ++i ) {
+ for( set<string>::const_iterator i = dependencies.fields.begin();
+ i != dependencies.fields.end();
+ ++i ) {
bab << *i;
}
ASSERT_EQUALS( expectedDependencies, bab.arr() );
+ ASSERT_EQUALS( false, dependencies.needWholeDocument );
+ ASSERT_EQUALS( false, dependencies.needTextScore );
}
};
@@ -2098,9 +2110,9 @@ namespace ExpressionTests {
intrusive_ptr<ExpressionObject> expression = ExpressionObject::createRoot();
expression->includePath( "a" );
assertDependencies( BSON_ARRAY( "_id" << "a" ), expression, true );
- set<string> unused;
+ DepsTracker unused;
// 'path' must be provided for inclusion expressions.
- ASSERT_THROWS( expression->addDependencies( unused ), UserException );
+ ASSERT_THROWS( expression->addDependencies( &unused ), UserException );
}
};