/** * Copyright (C) 2016 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/pipeline/parsed_exclusion_projection.h" #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/field_path.h" #include "mongo/db/pipeline/value.h" #include "mongo/stdx/memory.h" namespace mongo { namespace parsed_aggregation_projection { // // ExclusionNode. // ExclusionNode::ExclusionNode(std::string pathToNode) : _pathToNode(std::move(pathToNode)) {} Document ExclusionNode::serialize() const { MutableDocument output; for (auto&& excludedField : _excludedFields) { output.addField(excludedField, Value(false)); } for (auto&& childPair : _children) { output.addField(childPair.first, Value(childPair.second->serialize())); } return output.freeze(); } void ExclusionNode::excludePath(FieldPath path) { if (path.getPathLength() == 1) { _excludedFields.insert(path.fullPath()); return; } addOrGetChild(path.getFieldName(0))->excludePath(path.tail()); } Document ExclusionNode::applyProjection(Document input) const { MutableDocument output(input); for (auto&& field : _excludedFields) { output.remove(field); } for (auto&& childPair : _children) { output[childPair.first] = childPair.second->applyProjectionToValue(input[childPair.first]); } return output.freeze(); } ExclusionNode* ExclusionNode::addOrGetChild(FieldPath fieldPath) { invariant(fieldPath.getPathLength() == 1); auto child = getChild(fieldPath.fullPath()); return child ? child : addChild(fieldPath.fullPath()); } ExclusionNode* ExclusionNode::getChild(std::string field) const { auto it = _children.find(field); return it == _children.end() ? nullptr : it->second.get(); } ExclusionNode* ExclusionNode::addChild(std::string field) { auto pathToChild = _pathToNode.empty() ? field : _pathToNode + "." + field; auto emplacedPair = _children.emplace( std::make_pair(std::move(field), stdx::make_unique(pathToChild))); // emplacedPair is a pair. invariant(emplacedPair.second); return emplacedPair.first->second.get(); } Value ExclusionNode::applyProjectionToValue(Value val) const { switch (val.getType()) { case BSONType::Object: return Value(applyProjection(val.getDocument())); case BSONType::Array: { // Apply exclusion to each element of the array. Note that numeric paths aren't treated // specially, and we will always apply the projection to each element in the array. // // For example, applying the projection {"a.1": 0} to the document // {a: [{b: 0, "1": 0}, {b: 1, "1": 1}]} will not result in {a: [{b: 0, "1": 0}]}, but // instead will result in {a: [{b: 0}, {b: 1}]}. std::vector values = val.getArray(); for (auto it = values.begin(); it != values.end(); it++) { *it = applyProjectionToValue(*it); } return Value(std::move(values)); } default: return val; } } // // ParsedExclusionProjection. // Document ParsedExclusionProjection::serialize(bool explain) const { return _root->serialize(); } Document ParsedExclusionProjection::applyProjection(Document inputDoc) const { return _root->applyProjection(inputDoc); } void ParsedExclusionProjection::parse(const BSONObj& spec, ExclusionNode* node, size_t depth) { for (auto elem : spec) { const auto fieldName = elem.fieldNameStringData().toString(); // A $ should have been detected in ParsedAggregationProjection's parsing before we get // here. invariant(fieldName[0] != '$'); switch (elem.type()) { case BSONType::Bool: case BSONType::NumberInt: case BSONType::NumberLong: case BSONType::NumberDouble: case BSONType::NumberDecimal: { // We have already verified this is an exclusion projection. invariant(!elem.trueValue()); node->excludePath(FieldPath(fieldName)); break; } case BSONType::Object: { // This object represents a nested projection specification, like the sub-object in // {a: {b: 0, c: 0}} or {"a.b": {c: 0}}. ExclusionNode* child; if (elem.fieldNameStringData().find('.') == std::string::npos) { child = node->addOrGetChild(fieldName); } else { // A dotted field is not allowed in a sub-object, and should have been detected // in ParsedAggregationProjection's parsing before we get here. invariant(depth == 0); // We need to keep adding children to our tree until we create a child that // represents this dotted path. child = node; auto fullPath = FieldPath(fieldName); while (fullPath.getPathLength() > 1) { child = child->addOrGetChild(fullPath.getFieldName(0)); fullPath = fullPath.tail(); } // It is illegal to construct an empty FieldPath, so the above loop ends one // iteration too soon. Add the last path here. child = child->addOrGetChild(fullPath.fullPath()); } parse(elem.Obj(), child, depth + 1); break; } default: { MONGO_UNREACHABLE; } } } } } // namespace parsed_aggregation_projection } // namespace mongo