diff options
author | Mathias Stearn <mathias@10gen.com> | 2013-12-20 14:57:18 -0500 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2014-01-21 12:55:49 -0500 |
commit | d0037946dc103ffa648f7e8937f2c55351b03c53 (patch) | |
tree | 4b249ff3088fe64a234754b513e771f7acdaa8b6 | |
parent | 8bab6b0c9fad64286c22d928bbe8ebfae7e8b2c4 (diff) | |
download | mongo-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/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/pipeline/dependencies.cpp | 175 | ||||
-rw-r--r-- | src/mongo/db/pipeline/dependencies.h | 77 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source.cpp | 138 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source.h | 54 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_cursor.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_group.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_out.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_project.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_sort.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_unwind.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 27 | ||||
-rw-r--r-- | src/mongo/db/pipeline/pipeline_d.cpp | 84 | ||||
-rw-r--r-- | src/mongo/dbtests/documentsourcetests.cpp | 135 | ||||
-rw-r--r-- | src/mongo/dbtests/expressiontests.cpp | 54 |
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 ); } }; |