summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/dependencies.cpp
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 /src/mongo/db/pipeline/dependencies.cpp
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.
Diffstat (limited to 'src/mongo/db/pipeline/dependencies.cpp')
-rw-r--r--src/mongo/db/pipeline/dependencies.cpp175
1 files changed, 175 insertions, 0 deletions
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);
+ }
+}