diff options
Diffstat (limited to 'src/mongo/db/pipeline/document_source_lookup.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/document_source_lookup.cpp | 176 |
1 files changed, 37 insertions, 139 deletions
diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp index ca9be5509bf..cfb885e94c5 100644 --- a/src/mongo/db/pipeline/document_source_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_lookup.cpp @@ -35,12 +35,10 @@ #include "mongo/db/exec/document_value/value.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression_algo.h" -#include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/aggregation_request_helper.h" #include "mongo/db/pipeline/document_path_support.h" #include "mongo/db/pipeline/document_source_documents.h" #include "mongo/db/pipeline/document_source_merge_gen.h" -#include "mongo/db/pipeline/document_source_queue.h" #include "mongo/db/pipeline/document_source_sort.h" #include "mongo/db/pipeline/expression.h" #include "mongo/db/pipeline/expression_context.h" @@ -110,8 +108,7 @@ NamespaceString parseLookupFromAndResolveNamespace(const BSONElement& elem, Stri str::stream() << "$lookup with syntax {from: {db:<>, coll:<>},..} is not supported for db: " << nss.db() << " and coll: " << nss.coll(), nss.isConfigDotCacheDotChunks() || nss == NamespaceString::kRsOplogNamespace || - nss == NamespaceString::kTenantMigrationOplogView || - nss == NamespaceString::kConfigsvrCollectionsNamespace); + nss == NamespaceString::kTenantMigrationOplogView); return nss; } @@ -180,20 +177,15 @@ DocumentSourceLookUp::DocumentSourceLookUp( // We append an additional BSONObj to '_resolvedPipeline' as a placeholder for the $match stage // we'll eventually construct from the input document. _resolvedPipeline.reserve(_resolvedPipeline.size() + 1); - - // Initialize the introspection pipeline before we insert the $match. This is okay because we do - // not use the introspection pipeline during/after query execution, which is when the $match is - // necessary. - initializeResolvedIntrospectionPipeline(); - _resolvedPipeline.push_back(BSON("$match" << BSONObj())); _fieldMatchPipelineIdx = _resolvedPipeline.size() - 1; + + initializeResolvedIntrospectionPipeline(); } std::vector<BSONObj> extractSourceStage(const std::vector<BSONObj>& pipeline) { if (!pipeline.empty() && (pipeline[0].hasField(DocumentSourceDocuments::kStageName) || - pipeline[0].hasField(DocumentSourceQueue::kStageName) || pipeline[0].hasField("$search"_sd))) { return {pipeline[0]}; } @@ -220,9 +212,8 @@ DocumentSourceLookUp::DocumentSourceLookUp( // the local/foreignField $match. It must next after $documents if present. auto sourceStages = extractSourceStage(pipeline); _resolvedPipeline.insert(_resolvedPipeline.end(), sourceStages.begin(), sourceStages.end()); - // Save the correct position of the $match, but wait to insert it until we have finished - // constructing the pipeline and created the introspection pipeline below. - _fieldMatchPipelineIdx = _resolvedPipeline.size(); + _resolvedPipeline.push_back(BSON("$match" << BSONObj())); + _fieldMatchPipelineIdx = _resolvedPipeline.size() - 1; // Add the user pipeline to '_resolvedPipeline' after any potential view prefix and $match _resolvedPipeline.insert( _resolvedPipeline.end(), pipeline.begin() + sourceStages.size(), pipeline.end()); @@ -247,25 +238,15 @@ DocumentSourceLookUp::DocumentSourceLookUp( _variablesParseState.defineVariable(varName)); } - // Initialize the introspection pipeline before we insert the $match (if applicable). This is - // okay because we only use the introspection pipeline for reference while doing query analysis - // and analyzing involved dependencies/variables/collections/constraints. We do not use the - // introspection pipeline during/after query execution, which is when the $match is necessary. - // It wouldn't hurt anything to include the $match in this pipeline, but we also use the - // introspection pipeline in serialization, so it would be a bit odd to include an extra empty - // $match. initializeResolvedIntrospectionPipeline(); - - // Finally, insert the $match placeholder if we need it. - if (_fieldMatchPipelineIdx) { - _resolvedPipeline.insert(_resolvedPipeline.begin() + *_fieldMatchPipelineIdx, - BSON("$match" << BSONObj())); - } } DocumentSourceLookUp::DocumentSourceLookUp(const DocumentSourceLookUp& original, const boost::intrusive_ptr<ExpressionContext>& newExpCtx) - : DocumentSource(kStageName, newExpCtx), + : DocumentSource( + kStageName, + newExpCtx ? newExpCtx + : original.pExpCtx->copyWith(original.pExpCtx->ns, original.pExpCtx->uuid)), _fromNs(original._fromNs), _resolvedNs(original._resolvedNs), _as(original._as), @@ -285,10 +266,10 @@ DocumentSourceLookUp::DocumentSourceLookUp(const DocumentSourceLookUp& original, _cache.emplace(internalDocumentSourceCursorBatchSizeBytes.load()); } if (original._matchSrc) { - _matchSrc = static_cast<DocumentSourceMatch*>(original._matchSrc->clone(pExpCtx).get()); + _matchSrc = static_cast<DocumentSourceMatch*>(original._matchSrc->clone().get()); } if (original._unwindSrc) { - _unwindSrc = static_cast<DocumentSourceUnwind*>(original._unwindSrc->clone(pExpCtx).get()); + _unwindSrc = static_cast<DocumentSourceUnwind*>(original._unwindSrc->clone().get()); } } @@ -418,11 +399,6 @@ StageConstraints DocumentSourceLookUp::constraints(Pipeline::SplitState pipeStat // This stage will only be on the shards pipeline if $lookup on sharded foreign collections // is allowed. hostRequirement = HostTypeRequirement::kAnyShard; - } else if (_fromNs == NamespaceString::kConfigsvrCollectionsNamespace) { - // This is an unsharded collection, but the primary shard would be the config server, and - // the config servers are not prepared to take queries. Instead, we'll merge on any of the - // other shards. - hostRequirement = HostTypeRequirement::kAnyShard; } else { // If the pipeline is unsplit or this stage is on the merging part of the pipeline, // when $lookup on sharded foreign collections is allowed, the foreign collection is @@ -578,7 +554,6 @@ std::unique_ptr<Pipeline, PipelineDeleter> DocumentSourceLookUp::buildPipeline( // If we don't have a cache, build and return the pipeline immediately. if (!_cache || _cache->isAbandoned()) { MakePipelineOptions pipelineOpts; - pipelineOpts.alreadyOptimized = false; pipelineOpts.optimize = true; pipelineOpts.attachCursorSource = true; pipelineOpts.validator = lookupPipeValidator; @@ -613,7 +588,6 @@ std::unique_ptr<Pipeline, PipelineDeleter> DocumentSourceLookUp::buildPipeline( // Construct the basic pipeline without a cache stage. Avoid optimizing here since we need to // add the cache first, as detailed below. MakePipelineOptions pipelineOpts; - pipelineOpts.alreadyOptimized = false; pipelineOpts.optimize = false; pipelineOpts.attachCursorSource = false; pipelineOpts.validator = lookupPipeValidator; @@ -669,59 +643,25 @@ std::unique_ptr<Pipeline, PipelineDeleter> DocumentSourceLookUp::buildPipeline( return pipeline; } -/** - * Method that looks for a DocumentSourceSequentialDocumentCache stage and calls optimizeAt() on - * it if it has yet to be optimized. - */ -void findAndOptimizeSequentialDocumentCache(Pipeline& pipeline) { - auto& container = pipeline.getSources(); - auto itr = (&container)->begin(); - while (itr != (&container)->end()) { - if (dynamic_cast<DocumentSourceSequentialDocumentCache*>(itr->get())) { - auto sequentialCache = dynamic_cast<DocumentSourceSequentialDocumentCache*>(itr->get()); - if (!sequentialCache->hasOptimizedPos()) { - sequentialCache->optimizeAt(itr, &container); - } - } - itr = std::next(itr); - } -} - void DocumentSourceLookUp::addCacheStageAndOptimize(Pipeline& pipeline) { - // Adds the cache to the end of the pipeline and calls optimizeContainer which will ensure the - // stages of the pipeline are in the correct and optimal order, before the cache runs - // doOptimizeAt. During the optimization process, the cache will either move itself to the - // correct position in the pipeline, or abandon itself if no suitable cache position exists. - // Once the cache is finished optimizing, the entire pipeline is optimized. - // - // When pipeline optimization is disabled, 'Pipeline::optimizePipeline()' exits early and so the - // cache would not be placed correctly. So we only add the cache when pipeline optimization is - // enabled. + // Add the cache stage at the end and optimize. During the optimization process, the cache will + // either move itself to the correct position in the pipeline, or will abandon itself if no + // suitable cache position exists. Do it only if pipeline optimization is enabled, otherwise + // Pipeline::optimizePipeline() will exit early and correct placement of the cache will not + // occur. if (auto fp = globalFailPointRegistry().find("disablePipelineOptimization"); fp && fp->shouldFail()) { _cache->abandon(); } else { - // The cache needs to see the full pipeline in its correct order in order to properly place - // itself, therefore we are adding it to the end of the pipeline, and calling - // optimizeContainer on the pipeline to ensure the rest of the pipeline is in its correct - // order before optimizing the cache. pipeline.addFinalSource( DocumentSourceSequentialDocumentCache::create(_fromExpCtx, _cache.get_ptr())); - - auto& container = pipeline.getSources(); - - Pipeline::optimizeContainer(&container); - - // We want to ensure the cache has been optimized prior to any calls to optimize(). - findAndOptimizeSequentialDocumentCache(pipeline); - - // Optimize the pipeline, with the cache in its correct position if it exists. - Pipeline::optimizeEachStage(&container); } + + pipeline.optimizePipeline(); } DocumentSource::GetModPathsReturn DocumentSourceLookUp::getModifiedPaths() const { - OrderedPathSet modifiedPaths{_as.fullPath()}; + std::set<std::string> modifiedPaths{_as.fullPath()}; if (_unwindSrc) { auto pathsModifiedByUnwind = _unwindSrc->getModifiedPaths(); invariant(pathsModifiedByUnwind.type == GetModPathsReturn::Type::kFiniteSet); @@ -1079,68 +1019,44 @@ void DocumentSourceLookUp::appendSpecificExecStats(MutableDocument& doc) const { doc["indexesUsed"] = Value{std::move(indexesUsedVec)}; } -void DocumentSourceLookUp::serializeToArray(std::vector<Value>& array, - const SerializationOptions& opts) const { +void DocumentSourceLookUp::serializeToArray( + std::vector<Value>& array, boost::optional<ExplainOptions::Verbosity> explain) const { + // Support alternative $lookup from config.cache.chunks* namespaces. auto fromValue = (pExpCtx->ns.db() == _fromNs.db()) - ? Value(opts.serializeIdentifier(_fromNs.coll())) - : Value(Document{{"db", opts.serializeIdentifier(_fromNs.db())}, - {"coll", opts.serializeIdentifier(_fromNs.coll())}}); + ? Value(_fromNs.coll()) + : Value(Document{{"db", _fromNs.db()}, {"coll", _fromNs.coll()}}); - MutableDocument output(Document{ - {getSourceName(), Document{{"from", fromValue}, {"as", opts.serializeFieldPath(_as)}}}}); + MutableDocument output( + Document{{getSourceName(), Document{{"from", fromValue}, {"as", _as.fullPath()}}}}); if (hasLocalFieldForeignFieldJoin()) { - output[getSourceName()]["localField"] = Value(opts.serializeFieldPath(_localField.value())); - output[getSourceName()]["foreignField"] = - Value(opts.serializeFieldPath(_foreignField.value())); + output[getSourceName()]["localField"] = Value(_localField->fullPath()); + output[getSourceName()]["foreignField"] = Value(_foreignField->fullPath()); } // Add a pipeline field if only-pipeline syntax was used (to ensure the output is valid $lookup // syntax) or if a $match was absorbed. - auto serializedPipeline = [&]() -> std::vector<BSONObj> { - if (!_userPipeline) { - return std::vector<BSONObj>{}; - } - if (opts.transformIdentifiers || - opts.literalPolicy != LiteralSerializationPolicy::kUnchanged) { - return Pipeline::parse(*_userPipeline, _fromExpCtx)->serializeToBson(opts); - } - if (opts.serializeForQueryAnalysis) { - // If we are in query analysis, encrypted fields will have been marked in the - // introspection pipeline, so we need to serialize that here. - return _resolvedIntrospectionPipeline->serializeToBson(opts); - } - return *_userPipeline; - }(); + auto pipeline = _userPipeline.get_value_or(std::vector<BSONObj>()); if (_additionalFilter) { - auto serializedFilter = [&]() -> BSONObj { - if (opts.transformIdentifiers || - opts.literalPolicy != LiteralSerializationPolicy::kUnchanged) { - auto filter = - uassertStatusOK(MatchExpressionParser::parse(*_additionalFilter, pExpCtx)); - return filter->serialize(opts); - } - return *_additionalFilter; - }(); - serializedPipeline.emplace_back(BSON("$match" << serializedFilter)); + pipeline.emplace_back(BSON("$match" << *_additionalFilter)); } - if (!hasLocalFieldForeignFieldJoin() || serializedPipeline.size() > 0) { + if (!hasLocalFieldForeignFieldJoin() || pipeline.size() > 0) { MutableDocument exprList; for (auto letVar : _letVariables) { - exprList.addField(opts.serializeFieldPathFromString(letVar.name), - letVar.expression->serialize(opts)); + exprList.addField(letVar.name, + letVar.expression->serialize(static_cast<bool>(explain))); } output[getSourceName()]["let"] = Value(exprList.freeze()); - output[getSourceName()]["pipeline"] = Value(serializedPipeline); + output[getSourceName()]["pipeline"] = Value(pipeline); } if (_hasExplicitCollation) { output[getSourceName()]["_internalCollation"] = Value(_fromExpCtx->getCollatorBSON()); } - if (opts.verbosity) { + if (explain) { if (_unwindSrc) { const boost::optional<FieldPath> indexPath = _unwindSrc->indexPath(); output[getSourceName()]["unwinding"] = @@ -1148,11 +1064,8 @@ void DocumentSourceLookUp::serializeToArray(std::vector<Value>& array, << _unwindSrc->preserveNullAndEmptyArrays() << "includeArrayIndex" << (indexPath ? Value(indexPath->fullPath()) : Value()))); } - // Conflict is .get() vs .value(), changed in 6.1 during - // https://jira.mongodb.org/browse/SERVER-68246. Going to stick with 7.0's version of - // '.value()', I think there is no meaningful difference here since line 1080 check that the - // optional is set. - if (opts.verbosity.value() >= ExplainOptions::Verbosity::kExecStats) { + + if (explain.get() >= ExplainOptions::Verbosity::kExecStats) { appendSpecificExecStats(output); } @@ -1255,21 +1168,6 @@ void DocumentSourceLookUp::reattachToOperationContext(OperationContext* opCtx) { } } -bool DocumentSourceLookUp::validateOperationContext(const OperationContext* opCtx) const { - if (getContext()->opCtx != opCtx || (_fromExpCtx && _fromExpCtx->opCtx != opCtx)) { - return false; - } - - if (_pipeline) { - const auto& sources = _pipeline->getSources(); - return std::all_of(sources.begin(), sources.end(), [opCtx](const auto& s) { - return s->validateOperationContext(opCtx); - }); - } - - return true; -} - boost::intrusive_ptr<DocumentSource> DocumentSourceLookUp::createFromBson( BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) { uassert(ErrorCodes::FailedToParse, |
