diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index aef247fcc486b..0472717df1c8a 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -88,56 +88,83 @@ static const int kPerDocumentOverheadBytesUpperBound = 10; * suitable for forwarding to the targeted hosts. */ StatusWith> transformQueryForShards( - const QueryRequest& qr, bool appendGeoNearDistanceProjection) { + const QueryRequest& qr, bool appendGeoNearDistanceProjection, bool forwardToSingleShard) { + // For multi-shards queries: // If there is a limit, we forward the sum of the limit and the skip. + // For single-shard queries: + // If there is a limit, we forward the original limit. boost::optional newLimit; if (qr.getLimit()) { - long long newLimitValue; - if (mongoSignedAddOverflow64(*qr.getLimit(), qr.getSkip().value_or(0), &newLimitValue)) { - return Status( - ErrorCodes::Overflow, - str::stream() - << "sum of limit and skip cannot be represented as a 64-bit integer, limit: " - << *qr.getLimit() - << ", skip: " - << qr.getSkip().value_or(0)); + if (forwardToSingleShard) { + newLimit = *qr.getLimit(); + } else { + long long newLimitValue; + if (mongoSignedAddOverflow64(*qr.getLimit(), qr.getSkip().value_or(0), &newLimitValue)) { + return Status( + ErrorCodes::Overflow, + str::stream() + << "sum of limit and skip cannot be represented as a 64-bit integer, limit: " + << *qr.getLimit() + << ", skip: " + << qr.getSkip().value_or(0)); + } + newLimit = newLimitValue; } - newLimit = newLimitValue; } - // Similarly, if nToReturn is set, we forward the sum of nToReturn and the skip. + // For multi-shards queries: + // If nToReturn is set, we forward the sum of nToReturn and the skip. + // For single-shard queries: + // If nToReturn is set, we forward the original nToReturn. boost::optional newNToReturn; if (qr.getNToReturn()) { // !wantMore and ntoreturn mean the same as !wantMore and limit, so perform the conversion. if (!qr.wantMore()) { - long long newLimitValue; - if (mongoSignedAddOverflow64( - *qr.getNToReturn(), qr.getSkip().value_or(0), &newLimitValue)) { - return Status(ErrorCodes::Overflow, - str::stream() - << "sum of ntoreturn and skip cannot be represented as a 64-bit " + if (forwardToSingleShard) { + newLimit = *qr.getNToReturn(); + } else { + long long newLimitValue; + if (mongoSignedAddOverflow64( + *qr.getNToReturn(), qr.getSkip().value_or(0), &newLimitValue)) { + return Status(ErrorCodes::Overflow, + str::stream() + << "sum of ntoreturn and skip cannot be represented as a 64-bit " "integer, ntoreturn: " - << *qr.getNToReturn() - << ", skip: " - << qr.getSkip().value_or(0)); + << *qr.getNToReturn() + << ", skip: " + << qr.getSkip().value_or(0)); + } + newLimit = newLimitValue; } - newLimit = newLimitValue; } else { - long long newNToReturnValue; - if (mongoSignedAddOverflow64( - *qr.getNToReturn(), qr.getSkip().value_or(0), &newNToReturnValue)) { - return Status(ErrorCodes::Overflow, - str::stream() - << "sum of ntoreturn and skip cannot be represented as a 64-bit " - "integer, ntoreturn: " - << *qr.getNToReturn() - << ", skip: " - << qr.getSkip().value_or(0)); + if (forwardToSingleShard) { + newNToReturn = *qr.getNToReturn(); + } else { + long long newNToReturnValue; + if (mongoSignedAddOverflow64( + *qr.getNToReturn(), qr.getSkip().value_or(0), &newNToReturnValue)) { + return Status(ErrorCodes::Overflow, + str::stream() + << "sum of ntoreturn and skip cannot be represented as a 64-bit " + "integer, ntoreturn: " + << *qr.getNToReturn() + << ", skip: " + << qr.getSkip().value_or(0)); + } + newNToReturn = newNToReturnValue; } - newNToReturn = newNToReturnValue; } } + // For single-shard queries, we forward the original skip to mongod. + // For multi-shards queries or no skip specified, we forward none skip + boost::optional newSkip; + if (qr.getSkip() && forwardToSingleShard) { + newSkip = *qr.getSkip(); + } else { + newSkip = boost::none; + } + // If there is a sort other than $natural, we send a sortKey meta-projection to the remote node. BSONObj newProjection = qr.getProj(); if (!qr.getSort().isEmpty() && !qr.getSort()["$natural"]) { @@ -157,7 +184,7 @@ StatusWith> transformQueryForShards( auto newQR = stdx::make_unique(qr); newQR->setProj(newProjection); - newQR->setSkip(boost::none); + newQR->setSkip(newSkip); newQR->setLimit(newLimit); newQR->setNToReturn(newNToReturn); @@ -180,9 +207,10 @@ std::vector> constructRequestsForShards( const std::set& shardIds, const CanonicalQuery& query, bool appendGeoNearDistanceProjection, - boost::optional atClusterTime) { + boost::optional atClusterTime, + bool forwardToSingleShard) { const auto qrToForward = uassertStatusOK( - transformQueryForShards(query.getQueryRequest(), appendGeoNearDistanceProjection)); + transformQueryForShards(query.getQueryRequest(), appendGeoNearDistanceProjection, forwardToSingleShard)); if (atClusterTime) { auto readConcernAtClusterTime = @@ -278,11 +306,19 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, // Tailable cursors can't have a sort, which should have already been validated. invariant(params.sort.isEmpty() || !query.getQueryRequest().isTailable()); + bool forwardToSingleShard = false; + if (shardIds.size() == 1) { + LOG(3) << "cluster find command: " << query.toString() <<", forward to a single shard"; + forwardToSingleShard = true; + // Skip nothing in mongos, as skip operation is done in mongod if needed. + params.skip = boost::none; + } + // Construct the requests that we will use to establish cursors on the targeted shards, // attaching the shardVersion and txnNumber, if necessary. auto requests = constructRequestsForShards( - opCtx, routingInfo, shardIds, query, appendGeoNearDistanceProjection, atClusterTime); + opCtx, routingInfo, shardIds, query, appendGeoNearDistanceProjection, atClusterTime, forwardToSingleShard); // Establish the cursors with a consistent shardVersion across shards.