ExpressionContext::ExpressionContext(OperationContext* opCtx, const AggregationRequest& request, std::unique_ptr<CollatorInterface> collator, std::shared_ptr<MongoProcessInterface> processInterface, StringMap<ResolvedNamespace> resolvedNamespaces, boost::optional<UUID> collUUID) : ExpressionContext(opCtx, collator.get()) { explain = request.getExplain(); comment = request.getComment(); fromMongos = request.isFromMongos(); needsMerge = request.needsMerge(); mergeByPBRT = request.mergeByPBRT(); allowDiskUse = request.shouldAllowDiskUse(); bypassDocumentValidation = request.shouldBypassDocumentValidation(); ns = request.getNamespaceString(); mongoProcessInterface = std::move(processInterface); collation = request.getCollation(); _ownedCollator = std::move(collator); _resolvedNamespaces = std::move(resolvedNamespaces); uuid = std::move(collUUID); if (request.getRuntimeConstants()) { variables.setRuntimeConstants(request.getRuntimeConstants().get()); } else { variables.generateRuntimeConstants(opCtx); } }
Status runAggregate(OperationContext* opCtx, const NamespaceString& origNss, const AggregationRequest& request, const BSONObj& cmdObj, BSONObjBuilder& result) { // For operations on views, this will be the underlying namespace. NamespaceString nss = request.getNamespaceString(); // The collation to use for this aggregation. boost::optional to distinguish between the case // where the collation has not yet been resolved, and where it has been resolved to nullptr. boost::optional<std::unique_ptr<CollatorInterface>> collatorToUse; unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec; boost::intrusive_ptr<ExpressionContext> expCtx; Pipeline* unownedPipeline; auto curOp = CurOp::get(opCtx); { const LiteParsedPipeline liteParsedPipeline(request); // Check whether the parsed pipeline supports the given read concern. liteParsedPipeline.assertSupportsReadConcern(opCtx, request.getExplain()); if (liteParsedPipeline.hasChangeStream()) { nss = NamespaceString::kRsOplogNamespace; // If the read concern is not specified, upgrade to 'majority' and wait to make sure we // have a snapshot available. if (!repl::ReadConcernArgs::get(opCtx).hasLevel()) { const repl::ReadConcernArgs readConcern( repl::ReadConcernLevel::kMajorityReadConcern); uassertStatusOK(waitForReadConcern(opCtx, readConcern, true)); } if (!origNss.isCollectionlessAggregateNS()) { // AutoGetCollectionForReadCommand will raise an error if 'origNss' is a view. AutoGetCollectionForReadCommand origNssCtx(opCtx, origNss); // Resolve the collator to either the user-specified collation or the default // collation of the collection on which $changeStream was invoked, so that we do not // end up resolving the collation on the oplog. invariant(!collatorToUse); Collection* origColl = origNssCtx.getCollection(); collatorToUse.emplace(resolveCollator(opCtx, request, origColl)); } } const auto& pipelineInvolvedNamespaces = liteParsedPipeline.getInvolvedNamespaces(); // If emplaced, AutoGetCollectionForReadCommand will throw if the sharding version for this // connection is out of date. If the namespace is a view, the lock will be released before // re-running the expanded aggregation. boost::optional<AutoGetCollectionForReadCommand> ctx; // If this is a collectionless aggregation, we won't create 'ctx' but will still need an // AutoStatsTracker to record CurOp and Top entries. boost::optional<AutoStatsTracker> statsTracker; // If this is a collectionless aggregation with no foreign namespaces, we don't want to // acquire any locks. Otherwise, lock the collection or view. if (nss.isCollectionlessAggregateNS() && pipelineInvolvedNamespaces.empty()) { statsTracker.emplace(opCtx, nss, Top::LockType::NotLocked, 0); } else { ctx.emplace(opCtx, nss, AutoGetCollection::ViewMode::kViewsPermitted); } Collection* collection = ctx ? ctx->getCollection() : nullptr; // The collator may already have been set if this is a $changeStream pipeline. If not, // resolve the collator to either the user-specified collation or the collection default. if (!collatorToUse) { collatorToUse.emplace(resolveCollator(opCtx, request, collection)); } // If this is a view, resolve it by finding the underlying collection and stitching view // pipelines and this request's pipeline together. We then release our locks before // recursively calling runAggregate(), which will re-acquire locks on the underlying // collection. (The lock must be released because recursively acquiring locks on the // database will prohibit yielding.) if (ctx && ctx->getView() && !liteParsedPipeline.startsWithCollStats()) { invariant(nss != NamespaceString::kRsOplogNamespace); invariant(!nss.isCollectionlessAggregateNS()); // Check that the default collation of 'view' is compatible with the operation's // collation. The check is skipped if the request did not specify a collation. if (!request.getCollation().isEmpty()) { invariant(collatorToUse); // Should already be resolved at this point. if (!CollatorInterface::collatorsMatch(ctx->getView()->defaultCollator(), collatorToUse->get())) { return {ErrorCodes::OptionNotSupportedOnView, "Cannot override a view's default collation"}; } } ViewShardingCheck::throwResolvedViewIfSharded(opCtx, ctx->getDb(), ctx->getView()); auto resolvedView = ctx->getDb()->getViewCatalog()->resolveView(opCtx, nss); if (!resolvedView.isOK()) { return resolvedView.getStatus(); } // With the view & collation resolved, we can relinquish locks. ctx.reset(); // Parse the resolved view into a new aggregation request. auto newRequest = resolvedView.getValue().asExpandedViewAggregation(request); auto newCmd = newRequest.serializeToCommandObj().toBson(); auto status = runAggregate(opCtx, origNss, newRequest, newCmd, result); { // Set the namespace of the curop back to the view namespace so ctx records // stats on this view namespace on destruction. stdx::lock_guard<Client> lk(*opCtx->getClient()); curOp->setNS_inlock(nss.ns()); } return status; } invariant(collatorToUse); expCtx.reset( new ExpressionContext(opCtx, request, std::move(*collatorToUse), std::make_shared<PipelineD::MongoDInterface>(opCtx), uassertStatusOK(resolveInvolvedNamespaces(opCtx, request)))); expCtx->tempDir = storageGlobalParams.dbpath + "/_tmp"; auto session = OperationContextSession::get(opCtx); expCtx->inSnapshotReadOrMultiDocumentTransaction = session && session->inSnapshotReadOrMultiDocumentTransaction(); auto pipeline = uassertStatusOK(Pipeline::parse(request.getPipeline(), expCtx)); // Check that the view's collation matches the collation of any views involved in the // pipeline. if (!pipelineInvolvedNamespaces.empty()) { invariant(ctx); auto pipelineCollationStatus = collatorCompatibleWithPipeline( opCtx, ctx->getDb(), expCtx->getCollator(), pipeline.get()); if (!pipelineCollationStatus.isOK()) { return pipelineCollationStatus; } } pipeline->optimizePipeline(); if (kDebugBuild && !expCtx->explain && !expCtx->fromMongos) { // Make sure all operations round-trip through Pipeline::serialize() correctly by // re-parsing every command in debug builds. This is important because sharded // aggregations rely on this ability. Skipping when fromMongos because this has // already been through the transformation (and this un-sets expCtx->fromMongos). pipeline = reparsePipeline(pipeline.get(), request, expCtx); } // Prepare a PlanExecutor to provide input into the pipeline, if needed. if (liteParsedPipeline.hasChangeStream()) { // If we are using a change stream, the cursor stage should have a simple collation, // regardless of what the user's collation was. std::unique_ptr<CollatorInterface> collatorForCursor = nullptr; auto collatorStash = expCtx->temporarilyChangeCollator(std::move(collatorForCursor)); PipelineD::prepareCursorSource(collection, nss, &request, pipeline.get()); } else { PipelineD::prepareCursorSource(collection, nss, &request, pipeline.get()); } // Optimize again, since there may be additional optimizations that can be done after adding // the initial cursor stage. Note this has to be done outside the above blocks to ensure // this process uses the correct collation if it does any string comparisons. pipeline->optimizePipeline(); // Transfer ownership of the Pipeline to the PipelineProxyStage. unownedPipeline = pipeline.get(); auto ws = make_unique<WorkingSet>(); auto proxy = make_unique<PipelineProxyStage>(opCtx, std::move(pipeline), ws.get()); // This PlanExecutor will simply forward requests to the Pipeline, so does not need to // yield or to be registered with any collection's CursorManager to receive invalidations. // The Pipeline may contain PlanExecutors which *are* yielding PlanExecutors and which *are* // registered with their respective collection's CursorManager auto statusWithPlanExecutor = PlanExecutor::make(opCtx, std::move(ws), std::move(proxy), nss, PlanExecutor::NO_YIELD); invariant(statusWithPlanExecutor.isOK()); exec = std::move(statusWithPlanExecutor.getValue()); { auto planSummary = Explain::getPlanSummary(exec.get()); stdx::lock_guard<Client> lk(*opCtx->getClient()); curOp->setPlanSummary_inlock(std::move(planSummary)); } } // Having released the collection lock, we can now create a cursor that returns results from the // pipeline. This cursor owns no collection state, and thus we register it with the global // cursor manager. The global cursor manager does not deliver invalidations or kill // notifications; the underlying PlanExecutor(s) used by the pipeline will be receiving // invalidations and kill notifications themselves, not the cursor we create here. ClientCursorParams cursorParams( std::move(exec), origNss, AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), opCtx->recoveryUnit()->getReadConcernLevel(), cmdObj); if (expCtx->tailableMode == TailableModeEnum::kTailableAndAwaitData) { cursorParams.setTailable(true); cursorParams.setAwaitData(true); } auto pin = CursorManager::getGlobalCursorManager()->registerCursor(opCtx, std::move(cursorParams)); ScopeGuard cursorFreer = MakeGuard(&ClientCursorPin::deleteUnderlying, &pin); // If both explain and cursor are specified, explain wins. if (expCtx->explain) { Explain::explainPipelineExecutor( pin.getCursor()->getExecutor(), *(expCtx->explain), &result); } else { // Cursor must be specified, if explain is not. const bool keepCursor = handleCursorCommand(opCtx, origNss, pin.getCursor(), request, result); if (keepCursor) { cursorFreer.Dismiss(); } } if (!expCtx->explain) { PlanSummaryStats stats; Explain::getSummaryStats(*(pin.getCursor()->getExecutor()), &stats); curOp->debug().setPlanSummaryMetrics(stats); curOp->debug().nreturned = stats.nReturned; } // Any code that needs the cursor pinned must be inside the try block, above. return Status::OK(); }