DocumentSourceLookUp::DocumentSourceLookUp(NamespaceString fromNs, std::string as, std::string localField, std::string foreignField, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) : DocumentSourceNeedsMongod(pExpCtx), _fromNs(std::move(fromNs)), _as(std::move(as)), _localField(std::move(localField)), _foreignField(foreignField), _foreignFieldFieldName(std::move(foreignField)) { const auto& resolvedNamespace = pExpCtx->getResolvedNamespace(_fromNs); _fromExpCtx = pExpCtx->copyWith(resolvedNamespace.ns); _fromPipeline = resolvedNamespace.pipeline; // We append an additional BSONObj to '_fromPipeline' as a placeholder for the $match stage // we'll eventually construct from the input document. _fromPipeline.reserve(_fromPipeline.size() + 1); _fromPipeline.push_back(BSONObj()); }
boost::optional<Document> PipelineS::MongoSInterface::lookupSingleDocument( const boost::intrusive_ptr<ExpressionContext>& expCtx, const NamespaceString& nss, UUID collectionUUID, const Document& filter, boost::optional<BSONObj> readConcern) { auto foreignExpCtx = expCtx->copyWith(nss, collectionUUID); // Create the find command to be dispatched to the shard in order to return the post-change // document. auto filterObj = filter.toBson(); BSONObjBuilder cmdBuilder; bool findCmdIsByUuid(foreignExpCtx->uuid); if (findCmdIsByUuid) { foreignExpCtx->uuid->appendToBuilder(&cmdBuilder, "find"); } else { cmdBuilder.append("find", nss.coll()); } cmdBuilder.append("filter", filterObj); cmdBuilder.append("comment", expCtx->comment); if (readConcern) { cmdBuilder.append(repl::ReadConcernArgs::kReadConcernFieldName, *readConcern); } auto shardResult = std::vector<ClusterClientCursorParams::RemoteCursor>(); auto findCmd = cmdBuilder.obj(); size_t numAttempts = 0; while (++numAttempts <= kMaxNumStaleVersionRetries) { // Verify that the collection exists, with the correct UUID. auto catalogCache = Grid::get(expCtx->opCtx)->catalogCache(); auto swRoutingInfo = getCollectionRoutingInfo(foreignExpCtx); if (swRoutingInfo == ErrorCodes::NamespaceNotFound) { return boost::none; } auto routingInfo = uassertStatusOK(std::move(swRoutingInfo)); if (findCmdIsByUuid && routingInfo.cm()) { // Find by UUID and shard versioning do not work together (SERVER-31946). In the // sharded case we've already checked the UUID, so find by namespace is safe. In the // unlikely case that the collection has been deleted and a new collection with the same // name created through a different mongos, the shard version will be detected as stale, // as shard versions contain an 'epoch' field unique to the collection. findCmd = findCmd.addField(BSON("find" << nss.coll()).firstElement()); findCmdIsByUuid = false; } // Get the ID and version of the single shard to which this query will be sent. auto shardInfo = getSingleTargetedShardForQuery(expCtx->opCtx, routingInfo, filterObj); // Dispatch the request. This will only be sent to a single shard and only a single result // will be returned. The 'establishCursors' method conveniently prepares the result into a // cursor response for us. try { shardResult = establishCursors( expCtx->opCtx, Grid::get(expCtx->opCtx)->getExecutorPool()->getArbitraryExecutor(), nss, ReadPreferenceSetting::get(expCtx->opCtx), {{shardInfo.first, appendShardVersion(findCmd, shardInfo.second)}}, false); break; } catch (const ExceptionFor<ErrorCodes::NamespaceNotFound>&) { // If it's an unsharded collection which has been deleted and re-created, we may get a // NamespaceNotFound error when looking up by UUID. return boost::none; } catch (const ExceptionForCat<ErrorCategory::StaleShardingError>&) { // If we hit a stale shardVersion exception, invalidate the routing table cache. catalogCache->onStaleConfigError(std::move(routingInfo)); continue; // Try again if allowed. } break; // Success! } invariant(shardResult.size() == 1u); auto& cursor = shardResult.front().cursorResponse; auto& batch = cursor.getBatch(); // We should have at most 1 result, and the cursor should be exhausted. uassert(ErrorCodes::InternalError, str::stream() << "Shard cursor was unexpectedly open after lookup: " << shardResult.front().hostAndPort << ", id: " << cursor.getCursorId(), cursor.getCursorId() == 0); uassert(ErrorCodes::TooManyMatchingDocuments, str::stream() << "found more than one document matching " << filter.toString() << " [" << batch.begin()->toString() << ", " << std::next(batch.begin())->toString() << "]", batch.size() <= 1u); return (!batch.empty() ? Document(batch.front()) : boost::optional<Document>{}); }