diff options
Diffstat (limited to 'src/mongo/db/pipeline/pipeline.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/pipeline.cpp | 164 |
1 files changed, 38 insertions, 126 deletions
diff --git a/src/mongo/db/pipeline/pipeline.cpp b/src/mongo/db/pipeline/pipeline.cpp index 30dbcba6290..97a896e5898 100644 --- a/src/mongo/db/pipeline/pipeline.cpp +++ b/src/mongo/db/pipeline/pipeline.cpp @@ -40,7 +40,6 @@ #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" #include "mongo/db/pipeline/accumulator.h" -#include "mongo/db/pipeline/change_stream_helpers.h" #include "mongo/db/pipeline/document_source.h" #include "mongo/db/pipeline/document_source_match.h" #include "mongo/db/pipeline/document_source_merge.h" @@ -106,32 +105,14 @@ void validateTopLevelPipeline(const Pipeline& pipeline) { // If the first stage is a $changeStream stage, then all stages in the pipeline must be // either $changeStream stages or allowlisted as being able to run in a change stream. - const bool isChangeStream = firstStageConstraints.isChangeStreamStage(); - // Record whether any of the stages in the pipeline is a $changeStreamSplitLargeEvent. - bool hasChangeStreamSplitLargeEventStage = false; - for (auto&& source : sources) { - uassert(ErrorCodes::IllegalOperation, - str::stream() << source->getSourceName() - << " is not permitted in a $changeStream pipeline", - !(isChangeStream && !source->constraints().isAllowedInChangeStream())); - // Check whether any stages must only be run in a change stream pipeline. - uassert(ErrorCodes::IllegalOperation, - str::stream() << source->getSourceName() - << " can only be used in a $changeStream pipeline", - !(source->constraints().requiresChangeStream() && !isChangeStream)); - // Check whether this is a change stream split stage. - if ("$changeStreamSplitLargeEvent"_sd == source->getSourceName()) { - hasChangeStreamSplitLargeEventStage = true; + if (firstStageConstraints.isChangeStreamStage()) { + for (auto&& source : sources) { + uassert(ErrorCodes::IllegalOperation, + str::stream() << source->getSourceName() + << " is not permitted in a $changeStream pipeline", + source->constraints().isAllowedInChangeStream()); } } - auto expCtx = pipeline.getContext(); - auto spec = isChangeStream ? expCtx->changeStreamSpec : boost::none; - auto hasSplitEventResumeToken = spec && - change_stream::resolveResumeTokenFromSpec(expCtx, *spec).fragmentNum.has_value(); - uassert(ErrorCodes::ChangeStreamFatalError, - "To resume from a split event, the $changeStream pipeline must include a " - "$changeStreamSplitLargeEvent stage", - !(hasSplitEventResumeToken && !hasChangeStreamSplitLargeEventStage)); } // Verify that usage of $searchMeta and $search is legal. @@ -174,12 +155,11 @@ Pipeline::~Pipeline() { std::unique_ptr<Pipeline, PipelineDeleter> Pipeline::clone( const boost::intrusive_ptr<ExpressionContext>& newExpCtx) const { - auto expCtx = newExpCtx ? newExpCtx : getContext(); SourceContainer clonedStages; for (auto&& stage : _sources) { - clonedStages.push_back(stage->clone(expCtx)); + clonedStages.push_back(stage->clone(newExpCtx)); } - return create(clonedStages, expCtx); + return create(clonedStages, newExpCtx ? newExpCtx : getContext()); } template <class T> @@ -251,43 +231,39 @@ std::unique_ptr<Pipeline, PipelineDeleter> Pipeline::create( } void Pipeline::validateCommon(bool alreadyOptimized) const { + size_t i = 0; + uassert(ErrorCodes::FailedToParse, str::stream() << "Pipeline length must be no longer than " << internalPipelineLengthLimit << " stages", static_cast<int>(_sources.size()) <= internalPipelineLengthLimit); - checkValidOperationContext(); - - // Keep track of stages which can only appear once. - std::set<StringData> singleUseStages; - - for (auto sourceIter = _sources.begin(); sourceIter != _sources.end(); ++sourceIter) { - auto& stage = *sourceIter; + for (auto&& stage : _sources) { auto constraints = stage->constraints(_splitState); // Verify that all stages adhere to their PositionRequirement constraints. uassert(40602, str::stream() << stage->getSourceName() << " is only valid as the first stage in a pipeline", - !(constraints.requiredPosition == PositionRequirement::kFirst && - sourceIter != _sources.begin())); + !(constraints.requiredPosition == PositionRequirement::kFirst && i != 0)); + uassert(40603, + str::stream() << stage->getSourceName() + << " is only valid as the first stage in an optimized pipeline", + !(alreadyOptimized && + constraints.requiredPosition == PositionRequirement::kFirstAfterOptimization && + i != 0)); - // TODO SERVER-73790: use PositionRequirement::kCustom to validate $match. auto matchStage = dynamic_cast<DocumentSourceMatch*>(stage.get()); uassert(17313, "$match with $text is only allowed as the first pipeline stage", - !(sourceIter != _sources.begin() && matchStage && matchStage->isTextQuery())); + !(i != 0 && matchStage && matchStage->isTextQuery())); uassert(40601, str::stream() << stage->getSourceName() << " can only be the final stage in the pipeline", !(constraints.requiredPosition == PositionRequirement::kLast && - std::next(sourceIter) != _sources.end())); - - // If the stage has a special requirement about its position, validate it. - if (constraints.requiredPosition == PositionRequirement::kCustom) { - stage->validatePipelinePosition(alreadyOptimized, sourceIter, _sources); - } + i != _sources.size() - 1)); + ++i; // Verify that we are not attempting to run a mongoS-only stage on mongoD. uassert(40644, @@ -299,17 +275,6 @@ void Pipeline::validateCommon(bool alreadyOptimized) const { str::stream() << "Stage not supported inside of a multi-document transaction: " << stage->getSourceName(), !(pCtx->opCtx->inMultiDocumentTransaction() && !constraints.isAllowedInTransaction())); - - // Verify that a stage which can only appear once doesn't appear more than that. - uassert(7183900, - str::stream() << stage->getSourceName() << " can only be used once in the pipeline", - !(constraints.canAppearOnlyOnceInPipeline && - !singleUseStages.insert(stage->getSourceName()).second)); - - tassert(7355707, - "If a stage is broadcast to all shard servers then it must be a data source.", - constraints.hostRequirement != HostTypeRequirement::kAllShardServers || - !constraints.requiresInputDocSource); } } @@ -318,29 +283,21 @@ void Pipeline::optimizePipeline() { if (MONGO_unlikely(disablePipelineOptimization.shouldFail())) { return; } + optimizeContainer(&_sources); - optimizeEachStage(&_sources); } void Pipeline::optimizeContainer(SourceContainer* container) { + SourceContainer optimizedSources; + SourceContainer::iterator itr = container->begin(); try { while (itr != container->end()) { invariant((*itr).get()); itr = (*itr).get()->optimizeAt(itr, container); } - } catch (DBException& ex) { - ex.addContext("Failed to optimize pipeline"); - throw; - } - stitch(container); -} - -void Pipeline::optimizeEachStage(SourceContainer* container) { - SourceContainer optimizedSources; - try { - // We should have our final number of stages. Optimize each individually. + // Once we have reached our final number of stages, optimize each individually. for (auto&& source : *container) { if (auto out = source->optimize()) { optimizedSources.push_back(out); @@ -381,9 +338,6 @@ void Pipeline::detachFromOperationContext() { for (auto&& source : _sources) { source->detachFromOperationContext(); } - - // Check for a null operation context to make sure that all children detached correctly. - checkValidOperationContext(); } void Pipeline::reattachToOperationContext(OperationContext* opCtx) { @@ -392,24 +346,6 @@ void Pipeline::reattachToOperationContext(OperationContext* opCtx) { for (auto&& source : _sources) { source->reattachToOperationContext(opCtx); } - - checkValidOperationContext(); -} - -bool Pipeline::validateOperationContext(const OperationContext* opCtx) const { - return std::all_of(_sources.begin(), _sources.end(), [this, opCtx](const auto& s) { - // All sources in a pipeline must share its expression context. Subpipelines may have a - // different expression context, but must point to the same operation context. Let the - // sources validate this themselves since they don't all have the same subpipelines, etc. - return s->getContext() == getContext() && s->validateOperationContext(opCtx); - }); -} - -void Pipeline::checkValidOperationContext() const { - tassert(7406000, - str::stream() - << "All DocumentSources and subpipelines must have the same operation context", - validateOperationContext(getContext()->opCtx)); } void Pipeline::dispose(OperationContext* opCtx) { @@ -461,19 +397,11 @@ bool Pipeline::needsMongosMerger() const { }); } -bool Pipeline::needsAllShardServers() const { - return std::any_of(_sources.begin(), _sources.end(), [&](const auto& stage) { - return stage->constraints().resolvedHostTypeRequirement(pCtx) == - HostTypeRequirement::kAllShardServers; - }); -} - bool Pipeline::needsShard() const { return std::any_of(_sources.begin(), _sources.end(), [&](const auto& stage) { auto hostType = stage->constraints().resolvedHostTypeRequirement(pCtx); return (hostType == HostTypeRequirement::kAnyShard || - hostType == HostTypeRequirement::kPrimaryShard || - hostType == HostTypeRequirement::kAllShardServers); + hostType == HostTypeRequirement::kPrimaryShard); }); } @@ -519,20 +447,20 @@ stdx::unordered_set<NamespaceString> Pipeline::getInvolvedCollections() const { } vector<Value> Pipeline::serializeContainer(const SourceContainer& container, - boost::optional<const SerializationOptions&> opts) { + boost::optional<ExplainOptions::Verbosity> explain) { vector<Value> serializedSources; for (auto&& source : container) { - source->serializeToArray(serializedSources, opts ? opts.get() : SerializationOptions()); + source->serializeToArray(serializedSources, explain); } return serializedSources; } - -vector<Value> Pipeline::serialize(boost::optional<const SerializationOptions&> opts) const { - return serializeContainer(_sources, opts); +vector<Value> Pipeline::serialize(boost::optional<ExplainOptions::Verbosity> explain) const { + return serializeContainer(_sources, explain); } -vector<BSONObj> Pipeline::serializeToBson(boost::optional<const SerializationOptions&> opts) const { - const auto serialized = serialize(opts); +vector<BSONObj> Pipeline::serializeToBson( + boost::optional<ExplainOptions::Verbosity> explain) const { + const auto serialized = serialize(explain); std::vector<BSONObj> asBson; asBson.reserve(serialized.size()); for (auto&& stage : serialized) { @@ -573,16 +501,16 @@ boost::optional<Document> Pipeline::getNext() { : boost::optional<Document>{nextResult.releaseDocument()}; } -vector<Value> Pipeline::writeExplainOps(const SerializationOptions& opts) const { +vector<Value> Pipeline::writeExplainOps(ExplainOptions::Verbosity verbosity) const { vector<Value> array; for (auto&& stage : _sources) { auto beforeSize = array.size(); - stage->serializeToArray(array, opts); + stage->serializeToArray(array, verbosity); auto afterSize = array.size(); // Append execution stats to the serialized stage if the specified verbosity is // 'executionStats' or 'allPlansExecution'. invariant(afterSize - beforeSize == 1u); - if (*opts.verbosity >= ExplainOptions::Verbosity::kExecStats) { + if (verbosity >= ExplainOptions::Verbosity::kExecStats) { auto serializedStage = array.back(); array.back() = appendCommonExecStats(serializedStage, stage->getCommonStats()); } @@ -673,8 +601,7 @@ Status Pipeline::_pipelineCanRunOnMongoS() const { auto hostRequirement = constraints.resolvedHostTypeRequirement(pCtx); const bool needsShard = (hostRequirement == HostTypeRequirement::kAnyShard || - hostRequirement == HostTypeRequirement::kPrimaryShard || - hostRequirement == HostTypeRequirement::kAllShardServers); + hostRequirement == HostTypeRequirement::kPrimaryShard); const bool mustWriteToDisk = (constraints.diskRequirement == DiskUseRequirement::kWritesPersistentData); @@ -761,31 +688,17 @@ boost::intrusive_ptr<DocumentSource> Pipeline::popFrontWithNameAndCriteria( return popFront(); } -void Pipeline::appendPipeline(std::unique_ptr<Pipeline, PipelineDeleter> otherPipeline) { - auto& otherPipelineSources = otherPipeline->getSources(); - while (!otherPipelineSources.empty()) { - _sources.push_back(std::move(otherPipelineSources.front())); - otherPipelineSources.pop_front(); - } - constexpr bool alreadyOptimized = false; - validateCommon(alreadyOptimized); - stitch(); -} - - std::unique_ptr<Pipeline, PipelineDeleter> Pipeline::makePipeline( const std::vector<BSONObj>& rawPipeline, const boost::intrusive_ptr<ExpressionContext>& expCtx, const MakePipelineOptions opts) { auto pipeline = Pipeline::parse(rawPipeline, expCtx, opts.validator); - bool alreadyOptimized = opts.alreadyOptimized; - if (opts.optimize) { pipeline->optimizePipeline(); - alreadyOptimized = true; } + constexpr bool alreadyOptimized = true; pipeline->validateCommon(alreadyOptimized); if (opts.attachCursorSource) { @@ -802,7 +715,6 @@ Pipeline::SourceContainer::iterator Pipeline::optimizeEndOfPipeline( // optimize, since otherwise calls to optimizeAt() will overrun these limits. auto endOfPipeline = Pipeline::SourceContainer(std::next(itr), container->end()); Pipeline::optimizeContainer(&endOfPipeline); - Pipeline::optimizeEachStage(&endOfPipeline); container->erase(std::next(itr), container->end()); container->splice(std::next(itr), endOfPipeline); |
