/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side 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 Server Side 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 #include #include "mongo/bson/bsonobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/aggregate_command_gen.h" #include "mongo/db/pipeline/lite_parsed_document_source.h" #include "mongo/db/read_concern_support_result.h" namespace mongo { /** * A semi-parsed version of a Pipeline, parsed just enough to determine information like what * foreign collections are involved. */ class LiteParsedPipeline { public: /** * Constructs a LiteParsedPipeline from the raw BSON stages given in 'request'. * * May throw a AssertionException if there is an invalid stage specification, although full * validation happens later, during Pipeline construction. */ LiteParsedPipeline(const AggregateCommandRequest& request) : LiteParsedPipeline(request.getNamespace(), request.getPipeline()) {} LiteParsedPipeline(const NamespaceString& nss, const std::vector& pipelineStages) { _stageSpecs.reserve(pipelineStages.size()); for (auto&& rawStage : pipelineStages) { _stageSpecs.push_back(LiteParsedDocumentSource::parse(nss, rawStage)); } } /** * Returns all foreign namespaces referenced by stages within this pipeline, if any. */ stdx::unordered_set getInvolvedNamespaces() const { stdx::unordered_set involvedNamespaces; for (auto&& spec : _stageSpecs) { auto stagesInvolvedNamespaces = spec->getInvolvedNamespaces(); involvedNamespaces.insert(stagesInvolvedNamespaces.begin(), stagesInvolvedNamespaces.end()); } return involvedNamespaces; } /** * Returns a vector of the foreign collections(s) referenced by this stage that potentially will * be involved in query execution, if any. For example, consider the pipeline: * * [{$lookup: {from: "bar", localField: "a", foreignField: "b", as: "output"}}, * {$unionWith: {coll: "foo", pipeline: [...]}}]. * * Here, "foo" is not considered a foreign execution namespace because "$unionWith" cannot be * pushed down into the execution subsystem underneath the leading cursor stage, while "bar" * is considered one because "$lookup" can be pushed down in certain cases. */ std::vector getForeignExecutionNamespaces() const { stdx::unordered_set nssSet; for (auto&& spec : _stageSpecs) { spec->getForeignExecutionNamespaces(nssSet); } return {nssSet.begin(), nssSet.end()}; } /** * Returns a list of the priviliges required for this pipeline. */ PrivilegeVector requiredPrivileges(bool isMongos, bool bypassDocumentValidation) const { PrivilegeVector requiredPrivileges; for (auto&& spec : _stageSpecs) { Privilege::addPrivilegesToPrivilegeVector( &requiredPrivileges, spec->requiredPrivileges(isMongos, bypassDocumentValidation)); } return requiredPrivileges; } /** * Returns true if the pipeline begins with a $collStats stage. */ bool startsWithCollStats() const { return !_stageSpecs.empty() && _stageSpecs.front()->isCollStats(); } /** * Returns true if the pipeline begins with a $indexStats stage. */ bool startsWithIndexStats() const { return !_stageSpecs.empty() && _stageSpecs.front()->isIndexStats(); } /** * Returns true if the pipeline begins with a $documents stage. */ bool startsWithDocuments() const { return !_stageSpecs.empty() && _stageSpecs.front()->isDocuments(); } /** * Returns true if the pipeline has a $changeStream stage. */ bool hasChangeStream() const { return std::any_of(_stageSpecs.begin(), _stageSpecs.end(), [](auto&& spec) { return spec->isChangeStream(); }); } /** * Returns false if the pipeline has any stages which cannot be passed through to the shards. */ bool allowedToPassthroughFromMongos() const { return std::all_of(_stageSpecs.cbegin(), _stageSpecs.cend(), [](const auto& spec) { return spec->allowedToPassthroughFromMongos(); }); } /** * Returns false if at least one of the stages does not allow the involved namespace 'nss' to be * sharded. */ bool allowShardedForeignCollection(NamespaceString nss, bool isMultiDocumentTransaction) const { return std::all_of(_stageSpecs.begin(), _stageSpecs.end(), [&nss, isMultiDocumentTransaction](auto&& spec) { return spec->allowShardedForeignCollection( nss, isMultiDocumentTransaction); }); } /** * Verifies that this pipeline is allowed to run with the specified read concern level. */ ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level, bool isImplicitDefault, boost::optional explain, bool enableMajorityReadConcern) const; /** * Checks that all of the stages in this pipeline are allowed to run with the specified read * concern level. Does not do any pipeline global checks. */ ReadConcernSupportResult sourcesSupportReadConcern(repl::ReadConcernLevel level, bool isImplicitDefault) const; /** * Verifies that this pipeline is allowed to run in a multi-document transaction. This ensures * that each stage is compatible, and throws a UserException if not. This should only be called * if the caller has determined the current operation is part of a transaction. */ void assertSupportsMultiDocumentTransaction( boost::optional explain) const; /** * Verifies that this pipeline is allowed to run with the read concern from the provided opCtx. * Used only when asserting is the desired behavior, otherwise use supportsReadConcern instead. */ void assertSupportsReadConcern(OperationContext* opCtx, boost::optional explain) const; /** * Perform checks that verify that the LitePipe is valid. Note that this function must be called * before forwarding an aggregation command on an unsharded collection, in order to verify that * the involved namespaces are allowed to be sharded. */ void verifyIsSupported(OperationContext* opCtx, std::function isSharded, boost::optional explain, bool enableMajorityReadConcern) const; /** * Returns true if the first stage in the pipeline does not require an input source. */ bool startsWithInitialSource() const { return !_stageSpecs.empty() && _stageSpecs.front()->isInitialSource(); } /** * Increments global stage counters corresponding to the stages in this lite parsed pipeline. */ void tickGlobalStageCounters() const; /** * Verifies that the pipeline contains valid stages. Optionally calls * 'validatePipelineStagesforAPIVersion' with 'opCtx'. */ void validate(const OperationContext* opCtx, bool performApiVersionChecks = true) const; private: std::vector> _stageSpecs; }; } // namespace mongo