/**
* 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 .
*
* 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/platform/basic.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 {
using std::set;
using std::string;
using std::vector;
namespace str = mongoutils::str;
BSONObj DepsTracker::toProjection() const {
if (fields.empty() && !needWholeDocument) {
if (_needTextScore) {
// We only need the text score, but there is no easy way to express this in the query
// projection language. We use $noFieldsNeeded with a textScore meta-projection since
// this is an inclusion projection which will exclude all existing fields but add the
// textScore metadata.
return BSON("_id" << 0 << "$noFieldsNeeded" << 1 << Document::metaFieldTextScore
<< BSON("$meta"
<< "textScore"));
} else {
// We truly need no information (we are doing a count or something similar). In this
// case, the DocumentSourceCursor will know there aren't any dependencies, and we can
// ignore the documents returned from the query system. We pass an empty object as the
// projection so that we have a chance of using the COUNT_SCAN optimization.
return BSONObj();
}
}
BSONObjBuilder bb;
if (_needTextScore)
bb.append(Document::metaFieldTextScore,
BSON("$meta"
<< "textScore"));
if (needWholeDocument)
return bb.obj();
bool needId = false;
string last;
for (set::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 DepsTracker::toParsedDeps() const {
MutableDocument md;
if (needWholeDocument || _needTextScore) {
// can't use ParsedDeps in this case
return boost::none;
}
string last;
for (set::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, int nFieldsNeeded = -1);
// Handles array-typed values for ParsedDeps::extractFields
Value arrayHelper(const BSONObj& bson, const Document& neededFields) {
BSONObjIterator it(bson);
vector 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(std::move(values));
}
// Handles object-typed values including the top-level for ParsedDeps::extractFields
Document documentHelper(const BSONObj& bson, const Document& neededFields, int nFieldsNeeded) {
// We cache the number of top level fields, so don't need to re-compute it every time. For
// sub-documents, just scan for the number of fields.
if (nFieldsNeeded == -1) {
nFieldsNeeded = neededFields.size();
}
MutableDocument md(nFieldsNeeded);
BSONObjIterator it(bson);
while (it.more() && nFieldsNeeded > 0) {
auto bsonElement = it.next();
StringData fieldName = bsonElement.fieldNameStringData();
Value isNeeded = neededFields[fieldName];
if (isNeeded.missing())
continue;
--nFieldsNeeded; // Found a needed field.
if (isNeeded.getType() == Bool) {
md.addField(fieldName, Value(bsonElement));
} else {
dassert(isNeeded.getType() == Object);
if (bsonElement.type() == BSONType::Object) {
md.addField(
fieldName,
Value(documentHelper(bsonElement.embeddedObject(), isNeeded.getDocument())));
} else if (bsonElement.type() == BSONType::Array) {
md.addField(fieldName,
arrayHelper(bsonElement.embeddedObject(), isNeeded.getDocument()));
}
}
}
return md.freeze();
}
} // namespace
Document ParsedDeps::extractFields(const BSONObj& input) const {
return documentHelper(input, _fields, _nFields);
}
}