/** * 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. */ #pragma once #include #include "mongo/db/pipeline/expression.h" #include "mongo/db/pipeline/expression_context.h" #include "mongo/db/pipeline/parsed_aggregation_projection.h" #include "mongo/stdx/memory.h" #include "mongo/stdx/unordered_map.h" #include "mongo/stdx/unordered_set.h" namespace mongo { class FieldPath; class Value; namespace parsed_aggregation_projection { /** * A node used to define the parsed structure of an inclusion projection. Each InclusionNode * represents one 'level' of the parsed specification. The root InclusionNode represents all top * level inclusions or additions, with any child InclusionNodes representing dotted or nested * inclusions or additions. */ class InclusionNode { public: InclusionNode(std::string pathToNode = ""); /** * Optimize any computed expressions. */ void optimize(); /** * Serialize this projection. */ void serialize(MutableDocument* output, boost::optional explain) const; /** * Adds dependencies of any fields that need to be included, or that are used by any * expressions. */ void addDependencies(DepsTracker* deps) const; /** * Loops over 'inputDoc', extracting and appending any included fields into 'outputDoc'. This * will also copy over enough information to preserve the structure of the incoming document for * all the fields this projection cares about. * * For example, given an InclusionNode tree representing this projection: * {a: {b: 1, c: }, "d.e": } * calling applyInclusions() with an 'inputDoc' of * {a: [{b: 1, d: 1}, {b: 2, d: 2}], d: [{e: 1, f: 1}, {e: 1, f: 1}]} * and an empty 'outputDoc' will leave 'outputDoc' representing the document * {a: [{b: 1}, {b: 2}], d: [{}, {}]}. */ void applyInclusions(const Document& inputDoc, MutableDocument* outputDoc) const; /** * Add computed fields to 'outputDoc'. */ void addComputedFields(MutableDocument* outputDoc, const Document& root) const; /** * Creates the child if it doesn't already exist. 'field' is not allowed to be dotted. */ InclusionNode* addOrGetChild(std::string field); /** * Recursively adds 'path' into the tree as a computed field, creating any child nodes if * necessary. * * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in * the tree. For example, it is an error to add the path "a.b" as a computed field to a tree * which has already included the field "a". */ void addComputedField(const FieldPath& path, boost::intrusive_ptr expr); /** * Recursively adds 'path' into the tree as an included field, creating any child nodes if * necessary. * * 'path' is allowed to be dotted, and is assumed not to conflict with another path already in * the tree. For example, it is an error to include the path "a.b" from a tree which has already * added a computed field "a". */ void addIncludedField(const FieldPath& path); std::string getPath() const { return _pathToNode; } /** * Recursively add all paths that are preserved by this inclusion projection. */ void addPreservedPaths(std::set* preservedPaths) const; /** * Recursively adds all paths that are purely computed in this inclusion projection to * 'computedPaths'. * * Computed paths that are identified as the result of a simple rename are instead filled out in * 'renamedPaths'. Each entry in 'renamedPaths' maps from the path's new name to its old name * prior to application of this inclusion projection. */ void addComputedPaths(std::set* computedPaths, StringMap* renamedPaths) const; private: // Helpers for the Document versions above. These will apply the transformation recursively to // each element of any arrays, and ensure non-documents are handled appropriately. Value applyInclusionsToValue(Value inputVal) const; Value addComputedFields(Value inputVal, const Document& root) const; /** * Returns nullptr if no such child exists. */ InclusionNode* getChild(std::string field) const; /** * Adds a new InclusionNode as a child. 'field' cannot be dotted. */ InclusionNode* addChild(std::string field); /** * Returns true if this node or any child of this node contains a computed field. */ bool subtreeContainsComputedFields() const; std::string _pathToNode; // Our projection semantics are such that all field additions need to be processed in the order // specified. '_orderToProcessAdditionsAndChildren' tracks that order. // // For example, for the specification {a: , "b.c": , d: }, // we need to add the top level fields in the order "a", then "b", then "d". This ordering // information needs to be tracked separately, since "a" and "d" will be tracked via // '_expressions', and "b.c" will be tracked as a child InclusionNode in '_children'. For the // example above, '_orderToProcessAdditionsAndChildren' would be ["a", "b", "d"]. std::vector _orderToProcessAdditionsAndChildren; StringMap> _expressions; stdx::unordered_set _inclusions; // TODO use StringMap once SERVER-23700 is resolved. stdx::unordered_map> _children; }; /** * A ParsedInclusionProjection represents a parsed form of the raw BSON specification. * * This class is mostly a wrapper around an InclusionNode tree. It contains logic to parse a * specification object into the corresponding InclusionNode tree, but defers most execution logic * to the underlying tree. */ class ParsedInclusionProjection : public ParsedAggregationProjection { public: ParsedInclusionProjection(const boost::intrusive_ptr& expCtx) : ParsedAggregationProjection(expCtx), _root(new InclusionNode()) {} TransformerType getType() const final { return TransformerType::kInclusionProjection; } /** * Parses the projection specification given by 'spec', populating internal data structures. */ void parse(const BSONObj& spec) final; /** * Serialize the projection. */ Document serializeStageOptions(boost::optional explain) const final { MutableDocument output; if (_idExcluded) { output.addField("_id", Value(false)); } _root->serialize(&output, explain); return output.freeze(); } /** * Optimize any computed expressions. */ void optimize() final { _root->optimize(); } DocumentSource::GetDepsReturn addDependencies(DepsTracker* deps) const final { _root->addDependencies(deps); return DocumentSource::EXHAUSTIVE_FIELDS; } DocumentSource::GetModPathsReturn getModifiedPaths() const final { std::set preservedPaths; _root->addPreservedPaths(&preservedPaths); std::set computedPaths; StringMap renamedPaths; _root->addComputedPaths(&computedPaths, &renamedPaths); return {DocumentSource::GetModPathsReturn::Type::kAllExcept, std::move(preservedPaths), std::move(renamedPaths)}; } /** * Apply this exclusion projection to 'inputDoc'. * * All inclusions are processed before all computed fields. Computed fields will be added * afterwards in the order in which they were specified to the $project stage. * * Arrays will be traversed, with any dotted/nested exclusions or computed fields applied to * each element in the array. */ Document applyProjection(const Document& inputDoc) const final; /* * Checks whether the inclusion projection represented by the InclusionNode * tree is a subset of the object passed in. Projections that have any * computed or renamed fields are not considered a subset. */ bool isSubsetOfProjection(const BSONObj& proj) const final; private: /** * Attempts to parse 'objSpec' as an expression like {$add: [...]}. Adds a computed field to * '_root' and returns true if it was successfully parsed as an expression. Returns false if it * was not an expression specification. * * Throws an error if it was determined to be an expression specification, but failed to parse * as a valid expression. */ bool parseObjectAsExpression(StringData pathToObject, const BSONObj& objSpec, const VariablesParseState& variablesParseState); /** * Traverses 'subObj' and parses each field. Adds any included or computed fields at this level * to 'node'. */ void parseSubObject(const BSONObj& subObj, const VariablesParseState& variablesParseState, InclusionNode* node); // Not strictly necessary to track here, but makes serialization easier. bool _idExcluded = false; // The InclusionNode tree does most of the execution work once constructed. std::unique_ptr _root; }; } // namespace parsed_aggregation_projection } // namespace mongo