diff options
author | Irina Yatsenko <irina.yatsenko@mongodb.com> | 2021-07-07 15:44:54 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-07-16 05:38:55 +0000 |
commit | beeab6beaf18232e52bb3094f5f31fe83fbae2a4 (patch) | |
tree | dce5b9fefa813283212757dcf16f59e4b8bffe9e /src/mongo/db | |
parent | 23ecc48f89f4ec03d7b42e637c5969802efdb261 (diff) | |
download | mongo-beeab6beaf18232e52bb3094f5f31fe83fbae2a4.tar.gz |
SERVER-57391 Return error response to OP_QUERY and OP_GET_MORE messages
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/audit.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/audit.h | 16 | ||||
-rw-r--r-- | src/mongo/db/curop.cpp | 75 | ||||
-rw-r--r-- | src/mongo/db/curop.h | 21 | ||||
-rw-r--r-- | src/mongo/db/dbmessage.cpp | 63 | ||||
-rw-r--r-- | src/mongo/db/dbmessage.h | 88 | ||||
-rw-r--r-- | src/mongo/db/ops/write_ops_parsers_test.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.h | 17 | ||||
-rw-r--r-- | src/mongo/db/query/find.cpp | 673 | ||||
-rw-r--r-- | src/mongo/db/query/find.h | 16 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.h | 8 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 210 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.h | 1 |
15 files changed, 75 insertions, 1191 deletions
diff --git a/src/mongo/db/audit.cpp b/src/mongo/db/audit.cpp index 8b0867289d3..2f62a0d26b9 100644 --- a/src/mongo/db/audit.cpp +++ b/src/mongo/db/audit.cpp @@ -54,13 +54,6 @@ void logCommandAuthzCheck(Client* client, invariant(client); } -void logGetMoreAuthzCheck(Client* client, - const NamespaceString& ns, - long long cursorId, - ErrorCodes::Error result) { - invariant(client); -} - void logKillCursorsAuthzCheck(Client* client, const NamespaceString& ns, long long cursorId, @@ -68,13 +61,6 @@ void logKillCursorsAuthzCheck(Client* client, invariant(client); } -void logQueryAuthzCheck(Client* client, - const NamespaceString& ns, - const BSONObj& query, - ErrorCodes::Error result) { - invariant(client); -} - void logCreateUser(Client* client, const UserName& username, bool password, diff --git a/src/mongo/db/audit.h b/src/mongo/db/audit.h index 57bdfe600fd..2e4904e09df 100644 --- a/src/mongo/db/audit.h +++ b/src/mongo/db/audit.h @@ -172,14 +172,6 @@ void logCommandAuthzCheck(Client* client, ErrorCodes::Error result); /** - * Logs the result of an authorization check for an OP_GET_MORE wire protocol message. - */ -void logGetMoreAuthzCheck(Client* client, - const NamespaceString& ns, - long long cursorId, - ErrorCodes::Error result); - -/** * Logs the result of an authorization check for a killCursors command. */ void logKillCursorsAuthzCheck(Client* client, @@ -188,14 +180,6 @@ void logKillCursorsAuthzCheck(Client* client, ErrorCodes::Error result); /** - * Logs the result of an authorization check for an OP_QUERY wire protocol message. - */ -void logQueryAuthzCheck(Client* client, - const NamespaceString& ns, - const BSONObj& query, - ErrorCodes::Error result); - -/** * Logs the result of a createUser command. */ void logCreateUser(Client* client, diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 2ad744aefd5..aba407a169a 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -67,87 +67,12 @@ using std::string; namespace { -// Lists the $-prefixed query options that can be passed alongside a wrapped query predicate for -// OP_QUERY find. The $orderby field is omitted because "orderby" (no dollar sign) is also allowed, -// and this requires special handling. -const std::vector<const char*> kDollarQueryModifiers = { - "$hint", - "$comment", - "$max", - "$min", - "$returnKey", - "$showDiskLoc", - "$snapshot", - "$maxTimeMS", -}; - TimerStats oplogGetMoreStats; ServerStatusMetricField<TimerStats> displayBatchesReceived("repl.network.oplogGetMoresProcessed", &oplogGetMoreStats); } // namespace -BSONObj upconvertQueryEntry(const BSONObj& query, - const NamespaceString& nss, - int ntoreturn, - int ntoskip) { - BSONObjBuilder bob; - - bob.append("find", nss.coll()); - - // Whether or not the query predicate is wrapped inside a "query" or "$query" field so that - // other options can be passed alongside the predicate. - bool predicateIsWrapped = false; - - // Extract the query predicate. - BSONObj filter; - if (query["query"].isABSONObj()) { - predicateIsWrapped = true; - bob.appendAs(query["query"], "filter"); - } else if (query["$query"].isABSONObj()) { - predicateIsWrapped = true; - bob.appendAs(query["$query"], "filter"); - } else if (!query.isEmpty()) { - bob.append("filter", query); - } - - if (ntoskip) { - bob.append("skip", ntoskip); - } - if (ntoreturn) { - bob.append("ntoreturn", ntoreturn); - } - - // The remainder of the query options are only available if the predicate is passed in wrapped - // form. If the predicate is not wrapped, we're done. - if (!predicateIsWrapped) { - return bob.obj(); - } - - // Extract the sort. - if (auto elem = query["orderby"]) { - bob.appendAs(elem, "sort"); - } else if (auto elem = query["$orderby"]) { - bob.appendAs(elem, "sort"); - } - - // Add $-prefixed OP_QUERY modifiers, like $hint. - for (auto modifier : kDollarQueryModifiers) { - if (auto elem = query[modifier]) { - // Use "+ 1" to omit the leading dollar sign. - bob.appendAs(elem, modifier + 1); - } - } - - return bob.obj(); -} - -BSONObj upconvertGetMoreEntry(const NamespaceString& nss, CursorId cursorId, int ntoreturn) { - GetMoreCommandRequest getMoreRequest(cursorId, nss.coll().toString()); - getMoreRequest.setBatchSize(ntoreturn); - return getMoreRequest.toBSON({}); -} - /** * This type decorates a Client object with a stack of active CurOp objects. * diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h index 904f41ff73a..d95b56e41e9 100644 --- a/src/mongo/db/curop.h +++ b/src/mongo/db/curop.h @@ -512,13 +512,6 @@ public: } /** - * Returns true if this CurOp represents a non-command OP_QUERY request. - */ - bool isLegacyQuery() const { - return _networkOp == NetworkOp::dbQuery && !isCommand(); - } - - /** * Returns true if the current operation is known to be a command. */ bool isCommand() const { @@ -855,18 +848,4 @@ private: TickSource* _tickSource = nullptr; }; - -/** - * Upconverts a legacy query object such that it matches the format of the find command. - */ -BSONObj upconvertQueryEntry(const BSONObj& query, - const NamespaceString& nss, - int ntoreturn, - int ntoskip); - -/** - * Generates a getMore command object from the specified namespace, cursor ID and batchsize. - */ -BSONObj upconvertGetMoreEntry(const NamespaceString& nss, CursorId cursorId, int ntoreturn); - } // namespace mongo diff --git a/src/mongo/db/dbmessage.cpp b/src/mongo/db/dbmessage.cpp index 0fcb10aab3e..b8d94d2f866 100644 --- a/src/mongo/db/dbmessage.cpp +++ b/src/mongo/db/dbmessage.cpp @@ -154,12 +154,12 @@ Message makeMessage(NetworkOp op, Func&& bodyBuilder) { } } // namespace -Message makeQueryMessage(StringData ns, - BSONObj query, - int nToReturn, - int nToSkip, - const BSONObj* fieldsToReturn, - int queryOptions) { +Message makeDeprecatedQueryMessage(StringData ns, + BSONObj query, + int nToReturn, + int nToSkip, + const BSONObj* fieldsToReturn, + int queryOptions) { return makeMessage(dbQuery, [&](BufBuilder& b) { b.appendNum(queryOptions); b.appendStr(ns); @@ -171,7 +171,7 @@ Message makeQueryMessage(StringData ns, }); } -Message makeInsertMessage(StringData ns, const BSONObj* objs, size_t count, int flags) { +Message makeDeprecatedInsertMessage(StringData ns, const BSONObj* objs, size_t count, int flags) { return makeMessage(dbInsert, [&](BufBuilder& b) { int reservedFlags = 0; if (flags & InsertOption_ContinueOnError) @@ -186,7 +186,7 @@ Message makeInsertMessage(StringData ns, const BSONObj* objs, size_t count, int }); } -Message makeUpdateMessage(StringData ns, BSONObj query, BSONObj update, int flags) { +Message makeDeprecatedUpdateMessage(StringData ns, BSONObj query, BSONObj update, int flags) { return makeMessage(dbUpdate, [&](BufBuilder& b) { const int reservedFlags = 0; b.appendNum(reservedFlags); @@ -198,7 +198,7 @@ Message makeUpdateMessage(StringData ns, BSONObj query, BSONObj update, int flag }); } -Message makeRemoveMessage(StringData ns, BSONObj query, int flags) { +Message makeDeprecatedRemoveMessage(StringData ns, BSONObj query, int flags) { return makeMessage(dbDelete, [&](BufBuilder& b) { const int reservedFlags = 0; b.appendNum(reservedFlags); @@ -209,7 +209,7 @@ Message makeRemoveMessage(StringData ns, BSONObj query, int flags) { }); } -Message makeKillCursorsMessage(long long cursorId) { +Message makeDeprecatedKillCursorsMessage(long long cursorId) { return makeMessage(dbKillCursors, [&](BufBuilder& b) { b.appendNum((int)0); // reserved b.appendNum((int)1); // number @@ -217,7 +217,7 @@ Message makeKillCursorsMessage(long long cursorId) { }); } -Message makeGetMoreMessage(StringData ns, long long cursorId, int nToReturn, int flags) { +Message makeDeprecatedGetMoreMessage(StringData ns, long long cursorId, int nToReturn, int flags) { return makeMessage(dbGetMore, [&](BufBuilder& b) { b.appendNum(flags); b.appendStr(ns); @@ -226,32 +226,25 @@ Message makeGetMoreMessage(StringData ns, long long cursorId, int nToReturn, int }); } -OpQueryReplyBuilder::OpQueryReplyBuilder() : _buffer(32768) { - _buffer.skip(sizeof(QueryResult::Value)); -} +DbResponse makeErrorResponseToDeprecatedOpQuery(StringData errorMsg) { + BSONObjBuilder err; + err.append("$err", errorMsg); + err.append("code", 5739101); + err.append("ok", 0.0); + BSONObj errObj = err.done(); + + BufBuilder buffer(sizeof(QueryResult::Value) + errObj.objsize()); + buffer.skip(sizeof(QueryResult::Value)); + buffer.appendBuf(errObj.objdata(), errObj.objsize()); -Message OpQueryReplyBuilder::toQueryReply(int queryResultFlags, - int nReturned, - int startingFrom, - long long cursorId) { - QueryResult::View qr = _buffer.buf(); - qr.setResultFlags(queryResultFlags); - qr.msgdata().setLen(_buffer.len()); + QueryResult::View qr = buffer.buf(); + qr.setResultFlags(ResultFlag_ErrSet); + qr.msgdata().setLen(buffer.len()); qr.msgdata().setOperation(opReply); - qr.setCursorId(cursorId); - qr.setStartingFrom(startingFrom); - qr.setNReturned(nReturned); - return Message(_buffer.release()); -} + qr.setCursorId(0); + qr.setStartingFrom(0); + qr.setNReturned(1); -DbResponse replyToQuery(int queryResultFlags, - const void* data, - int size, - int nReturned, - int startingFrom, - long long cursorId) { - OpQueryReplyBuilder reply; - reply.bufBuilderForResults().appendBuf(data, size); - return DbResponse{reply.toQueryReply(queryResultFlags, nReturned, startingFrom, cursorId)}; + return DbResponse{Message(buffer.release())}; } } // namespace mongo diff --git a/src/mongo/db/dbmessage.h b/src/mongo/db/dbmessage.h index 992142f3ef1..63c78c261cb 100644 --- a/src/mongo/db/dbmessage.h +++ b/src/mongo/db/dbmessage.h @@ -387,12 +387,12 @@ public: /** * Builds a legacy OP_QUERY message. */ -Message makeQueryMessage(StringData ns, - BSONObj query, - int nToReturn, - int nToSkip, - const BSONObj* fieldsToReturn, - int queryOptions); +Message makeDeprecatedQueryMessage(StringData ns, + BSONObj query, + int nToReturn, + int nToSkip, + const BSONObj* fieldsToReturn, + int queryOptions); enum InsertOptions { /** With muli-insert keep processing inserts if one fails */ @@ -402,7 +402,10 @@ enum InsertOptions { /** * Builds a legacy OP_INSERT message. */ -Message makeInsertMessage(StringData ns, const BSONObj* objs, size_t count, int flags = 0); +Message makeDeprecatedInsertMessage(StringData ns, + const BSONObj* objs, + size_t count, + int flags = 0); enum UpdateOptions { /** Upsert - that is, insert the item if no matching item is found. */ @@ -419,7 +422,7 @@ enum UpdateOptions { /** * Builds a legacy OP_UPDATE message. */ -Message makeUpdateMessage(StringData ns, BSONObj query, BSONObj update, int flags = 0); +Message makeDeprecatedUpdateMessage(StringData ns, BSONObj query, BSONObj update, int flags = 0); enum RemoveOptions { /** only delete one option */ @@ -432,17 +435,20 @@ enum RemoveOptions { /** * Builds a legacy OP_REMOVE message. */ -Message makeRemoveMessage(StringData ns, BSONObj query, int flags = 0); +Message makeDeprecatedRemoveMessage(StringData ns, BSONObj query, int flags = 0); /** * Builds a legacy OP_KILLCURSORS message. */ -Message makeKillCursorsMessage(long long cursorId); +Message makeDeprecatedKillCursorsMessage(long long cursorId); /** - * Builds a legacy OP_GETMORE message. + * Builds a legacy OP_GET_MORE message. */ -Message makeGetMoreMessage(StringData ns, long long cursorId, int nToReturn, int flags = 0); +Message makeDeprecatedGetMoreMessage(StringData ns, + long long cursorId, + int nToReturn, + int flags = 0); /** * A response to a DbMessage. @@ -462,61 +468,7 @@ struct DbResponse { }; /** - * Prepares query replies to legacy finds (opReply to dbQuery) in place. This is also used for - * command responses that don't use the new dbMsg protocol. + * Helper to build an error DbResponse for OP_QUERY and OP_GET_MORE. */ -class OpQueryReplyBuilder { - OpQueryReplyBuilder(const OpQueryReplyBuilder&) = delete; - OpQueryReplyBuilder& operator=(const OpQueryReplyBuilder&) = delete; - -public: - OpQueryReplyBuilder(); - - /** - * Returns the BufBuilder that should be used for placing result objects. It will be positioned - * where the first (or next) object should go. - * - * You must finish the BSONObjBuilder that uses this (by destruction or calling doneFast()) - * before calling any more methods on this object. - */ - BufBuilder& bufBuilderForResults() { - return _buffer; - } - - /** - * Finishes the reply and returns the message buffer. - */ - Message toQueryReply(int queryResultFlags, - int nReturned, - int startingFrom = 0, - long long cursorId = 0); - - /** - * Similar to toQueryReply() but used for replying to a command. - */ - Message toCommandReply() { - return toQueryReply(0, 1); - } - -private: - BufBuilder _buffer; -}; - -/** - * Helper to build a DbResponse from a buffer containing an OP_QUERY response. - */ -DbResponse replyToQuery(int queryResultFlags, - const void* data, - int size, - int nReturned, - int startingFrom = 0, - long long cursorId = 0); - - -/** - * Helper to build a DbRespose for OP_QUERY with a single reply object. - */ -inline DbResponse replyToQuery(const BSONObj& obj, int queryResultFlags = 0) { - return replyToQuery(queryResultFlags, obj.objdata(), obj.objsize(), /*nReturned*/ 1); -} +DbResponse makeErrorResponseToDeprecatedOpQuery(StringData errorMsg); } // namespace mongo diff --git a/src/mongo/db/ops/write_ops_parsers_test.cpp b/src/mongo/db/ops/write_ops_parsers_test.cpp index d3dc3ebd63a..e8abac496fd 100644 --- a/src/mongo/db/ops/write_ops_parsers_test.cpp +++ b/src/mongo/db/ops/write_ops_parsers_test.cpp @@ -404,8 +404,8 @@ TEST(LegacyWriteOpsParsers, SingleInsert) { const std::string ns = "test.foo"; const BSONObj obj = BSON("x" << 1); for (bool continueOnError : {false, true}) { - auto message = - makeInsertMessage(ns, &obj, 1, continueOnError ? InsertOption_ContinueOnError : 0); + auto message = makeDeprecatedInsertMessage( + ns, &obj, 1, continueOnError ? InsertOption_ContinueOnError : 0); const auto op = InsertOp::parseLegacy(message); ASSERT_EQ(op.getNamespace().ns(), ns); ASSERT(!op.getWriteCommandRequestBase().getBypassDocumentValidation()); @@ -419,7 +419,7 @@ TEST(LegacyWriteOpsParsers, EmptyMultiInsertFails) { const std::string ns = "test.foo"; for (bool continueOnError : {false, true}) { auto objs = std::vector<BSONObj>{}; - auto message = makeInsertMessage( + auto message = makeDeprecatedInsertMessage( ns, objs.data(), objs.size(), (continueOnError ? InsertOption_ContinueOnError : 0)); ASSERT_THROWS_CODE( InsertOp::parseLegacy(message), AssertionException, ErrorCodes::InvalidLength); @@ -432,7 +432,7 @@ TEST(LegacyWriteOpsParsers, RealMultiInsert) { const BSONObj obj1 = BSON("x" << 1); for (bool continueOnError : {false, true}) { auto objs = std::vector<BSONObj>{obj0, obj1}; - auto message = makeInsertMessage( + auto message = makeDeprecatedInsertMessage( ns, objs.data(), objs.size(), continueOnError ? InsertOption_ContinueOnError : 0); const auto op = InsertOp::parseLegacy(message); ASSERT_EQ(op.getNamespace().ns(), ns); @@ -450,11 +450,11 @@ TEST(LegacyWriteOpsParsers, UpdateCommandRequest) { const BSONObj update = BSON("$inc" << BSON("x" << 1)); for (bool upsert : {false, true}) { for (bool multi : {false, true}) { - auto message = makeUpdateMessage(ns, - query, - update, - (upsert ? UpdateOption_Upsert : 0) | - (multi ? UpdateOption_Multi : 0)); + auto message = makeDeprecatedUpdateMessage(ns, + query, + update, + (upsert ? UpdateOption_Upsert : 0) | + (multi ? UpdateOption_Multi : 0)); const auto op = UpdateOp::parseLegacy(message); ASSERT_EQ(op.getNamespace().ns(), ns); ASSERT(!op.getWriteCommandRequestBase().getBypassDocumentValidation()); @@ -478,11 +478,11 @@ TEST(LegacyWriteOpsParsers, UpdateWithArrayUpdateFieldIsParsedAsReplacementStyle const BSONObj update = BSON_ARRAY(BSON("$addFields" << BSON("x" << 1))); for (bool upsert : {false, true}) { for (bool multi : {false, true}) { - auto message = makeUpdateMessage(ns, - query, - update, - (upsert ? UpdateOption_Upsert : 0) | - (multi ? UpdateOption_Multi : 0)); + auto message = makeDeprecatedUpdateMessage(ns, + query, + update, + (upsert ? UpdateOption_Upsert : 0) | + (multi ? UpdateOption_Multi : 0)); const auto op = UpdateOp::parseLegacy(message); ASSERT_EQ(op.getNamespace().ns(), ns); ASSERT(!op.getWriteCommandRequestBase().getBypassDocumentValidation()); @@ -502,7 +502,7 @@ TEST(LegacyWriteOpsParsers, Remove) { const std::string ns = "test.foo"; const BSONObj query = BSON("x" << 1); for (bool multi : {false, true}) { - auto message = makeRemoveMessage(ns, query, (multi ? 0 : RemoveOption_JustOne)); + auto message = makeDeprecatedRemoveMessage(ns, query, (multi ? 0 : RemoveOption_JustOne)); const auto op = DeleteOp::parseLegacy(message); ASSERT_EQ(op.getNamespace().ns(), ns); ASSERT(!op.getWriteCommandRequestBase().getBypassDocumentValidation()); diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index e7cfed36f53..b714624bd34 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -61,24 +61,6 @@ bool parsingCanProduceNoopMatchNodes(const ExtensionsCallback& extensionsCallbac // static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( OperationContext* opCtx, - const QueryMessage& qm, - const boost::intrusive_ptr<ExpressionContext>& expCtx, - const ExtensionsCallback& extensionsCallback, - MatchExpressionParser::AllowedFeatureSet allowedFeatures) { - bool explain = false; - // Make FindCommandRequest. - auto status = query_request_helper::fromLegacyQueryMessage(qm, &explain); - if (!status.isOK()) { - return status.getStatus(); - } - - return CanonicalQuery::canonicalize( - opCtx, std::move(status.getValue()), explain, expCtx, extensionsCallback, allowedFeatures); -} - -// static -StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( - OperationContext* opCtx, std::unique_ptr<FindCommandRequest> findCommand, bool explain, const boost::intrusive_ptr<ExpressionContext>& expCtx, diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h index d79d92e4068..1e2a29d6def 100644 --- a/src/mongo/db/query/canonical_query.h +++ b/src/mongo/db/query/canonical_query.h @@ -58,23 +58,6 @@ public: * * 'opCtx' must point to a valid OperationContext, but 'opCtx' does not need to outlive the * returned CanonicalQuery. - * - * Used for legacy find through the OP_QUERY message. - */ - static StatusWith<std::unique_ptr<CanonicalQuery>> canonicalize( - OperationContext* opCtx, - const QueryMessage& qm, - const boost::intrusive_ptr<ExpressionContext>& expCtx = nullptr, - const ExtensionsCallback& extensionsCallback = ExtensionsCallbackNoop(), - MatchExpressionParser::AllowedFeatureSet allowedFeatures = - MatchExpressionParser::kDefaultSpecialFeatures); - - /** - * If parsing succeeds, returns a std::unique_ptr<CanonicalQuery> representing the parsed - * query (which will never be NULL). If parsing fails, returns an error Status. - * - * 'opCtx' must point to a valid OperationContext, but 'opCtx' does not need to outlive the - * returned CanonicalQuery. */ static StatusWith<std::unique_ptr<CanonicalQuery>> canonicalize( OperationContext* opCtx, diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp index c4dac17cfec..0f791f2d11e 100644 --- a/src/mongo/db/query/find.cpp +++ b/src/mongo/db/query/find.cpp @@ -78,9 +78,6 @@ using std::unique_ptr; // Failpoint for checking whether we've received a getmore. MONGO_FAIL_POINT_DEFINE(failReceivedGetmore); -// Failpoint to keep a cursor pinned. -MONGO_FAIL_POINT_DEFINE(legacyGetMoreWaitWithCursor) - bool shouldSaveCursor(OperationContext* opCtx, const CollectionPtr& collection, PlanExecutor::ExecState finalState, @@ -148,674 +145,4 @@ void endQueryOp(OperationContext* opCtx, } } -namespace { - -/** - * Uses 'cursor' to fill out 'bb' with the batch of result documents to be returned by this getMore. - * - * Returns the number of documents in the batch in 'numResults', which must be initialized to - * zero by the caller. Returns the final ExecState returned by the cursor in *state. - * - * Throws an exception if the PlanExecutor encounters a failure. - */ -void generateBatch(int ntoreturn, - ClientCursor* cursor, - BufBuilder* bb, - std::uint64_t* numResults, - ResourceConsumption::DocumentUnitCounter* docUnitsReturned, - PlanExecutor::ExecState* state) { - PlanExecutor* exec = cursor->getExecutor(); - - try { - BSONObj obj; - while (!FindCommon::enoughForGetMore(ntoreturn, *numResults) && - PlanExecutor::ADVANCED == (*state = exec->getNext(&obj, nullptr))) { - - // If we can't fit this result inside the current batch, then we stash it for later. - if (!FindCommon::haveSpaceForNext(obj, *numResults, bb->len())) { - exec->enqueue(obj); - break; - } - - // Add result to output buffer. - bb->appendBuf((void*)obj.objdata(), obj.objsize()); - - // Count the result. - (*numResults)++; - - docUnitsReturned->observeOne(obj.objsize()); - } - } catch (DBException& exception) { - auto&& explainer = exec->getPlanExplainer(); - auto&& [stats, _] = explainer.getWinningPlanStats(ExplainOptions::Verbosity::kExecStats); - LOGV2_ERROR(20918, "getMore executor error", "stats"_attr = redact(stats)); - exception.addContext("Executor error during OP_GET_MORE"); - throw; - } -} - -Message makeCursorNotFoundResponse() { - const int initialBufSize = 512 + sizeof(QueryResult::Value); - BufBuilder bb(initialBufSize); - bb.skip(sizeof(QueryResult::Value)); - QueryResult::View qr = bb.buf(); - qr.msgdata().setLen(bb.len()); - qr.msgdata().setOperation(opReply); - qr.setResultFlags(ResultFlag_CursorNotFound); - qr.setCursorId(0); - qr.setStartingFrom(0); - qr.setNReturned(0); - return Message(bb.release()); -} - -} // namespace - -/** - * Called by db/instance.cpp. This is the getMore entry point. - */ -Message getMore(OperationContext* opCtx, - const char* ns, - int ntoreturn, - long long cursorid, - bool* exhaust, - bool* isCursorAuthorized) { - invariant(ntoreturn >= 0); - - LOGV2_DEBUG(20909, 5, "Running getMore", "cursorId"_attr = cursorid); - - CurOp& curOp = *CurOp::get(opCtx); - curOp.ensureStarted(); - - // For testing, we may want to fail if we receive a getmore. - if (MONGO_unlikely(failReceivedGetmore.shouldFail())) { - MONGO_UNREACHABLE; - } - - *exhaust = false; - - const NamespaceString nss(ns); - - ResourceConsumption::ScopedMetricsCollector scopedMetrics(opCtx, nss.db().toString()); - - // Cursors come in one of two flavors: - // - // - Cursors which read from a single collection, such as those generated via the find command. - // For these cursors, we hold the appropriate collection lock for the duration of the getMore - // using AutoGetCollectionForRead. These cursors have the 'kLockExternally' lock policy. - // - // - Cursors which may read from many collections, e.g. those generated via the aggregate - // command, or which do not read from a collection at all, e.g. those generated by the - // listIndexes command. We don't need to acquire locks to use these cursors, since they either - // manage locking themselves or don't access data protected by collection locks. These cursors - // have the 'kLocksInternally' lock policy. - // - // While we only need to acquire locks for 'kLockExternally' cursors, we need to create an - // AutoStatsTracker in either case. This is responsible for updating statistics in CurOp and - // Top. We avoid using AutoGetCollectionForReadCommand because we may need to drop and reacquire - // locks when the cursor is awaitData, but we don't want to update the stats twice. - UninterruptibleLockGuard noInterrupt(opCtx->lockState()); - boost::optional<AutoGetCollectionForReadMaybeLockFree> readLock; - boost::optional<AutoStatsTracker> statsTracker; - - // These are set in the QueryResult msg we return. - int resultFlags = ResultFlag_AwaitCapable; - - auto cursorManager = CursorManager::get(opCtx); - auto statusWithCursorPin = cursorManager->pinCursor(opCtx, cursorid); - if (statusWithCursorPin == ErrorCodes::CursorNotFound) { - return makeCursorNotFoundResponse(); - } - uassertStatusOK(statusWithCursorPin.getStatus()); - auto cursorPin = std::move(statusWithCursorPin.getValue()); - - // Set kMajorityCommitted before we instantiate readLock. We should not override readSource - // after storage snapshot is setup. - const auto replicationMode = repl::ReplicationCoordinator::get(opCtx)->getReplicationMode(); - if (replicationMode == repl::ReplicationCoordinator::modeReplSet && - cursorPin->getReadConcernArgs().getLevel() == - repl::ReadConcernLevel::kMajorityReadConcern) { - opCtx->recoveryUnit()->setTimestampReadSource(RecoveryUnit::ReadSource::kMajorityCommitted); - uassertStatusOK(opCtx->recoveryUnit()->majorityCommittedSnapshotAvailable()); - } - - opCtx->setExhaust(cursorPin->queryOptions() & QueryOption_Exhaust); - - if (cursorPin->getExecutor()->lockPolicy() == PlanExecutor::LockPolicy::kLocksInternally) { - if (!nss.isCollectionlessCursorNamespace()) { - AutoGetDb autoDb(opCtx, nss.db(), MODE_IS); - statsTracker.emplace(opCtx, - nss, - Top::LockType::NotLocked, - AutoStatsTracker::LogMode::kUpdateTopAndCurOp, - CollectionCatalog::get(opCtx)->getDatabaseProfileLevel(nss.db())); - auto view = - autoDb.getDb() ? ViewCatalog::get(autoDb.getDb())->lookup(opCtx, nss) : nullptr; - uassert( - ErrorCodes::CommandNotSupportedOnView, - str::stream() << "Namespace " << nss.ns() - << " is a view. OP_GET_MORE operations are not supported on views. " - << "Only clients which support the getMore command can be used to " - "query views.", - !view); - } - } else { - readLock.emplace(opCtx, nss); - statsTracker.emplace(opCtx, - nss, - Top::LockType::ReadLocked, - AutoStatsTracker::LogMode::kUpdateTopAndCurOp, - CollectionCatalog::get(opCtx)->getDatabaseProfileLevel(nss.db())); - - // This checks to make sure the operation is allowed on a replicated node. Since we are not - // passing in a query object (necessary to check SlaveOK query option), we allow reads - // whether we are PRIMARY or SECONDARY. - uassertStatusOK( - repl::ReplicationCoordinator::get(opCtx)->checkCanServeReadsFor(opCtx, nss, true)); - } - - std::uint64_t numResults = 0; - int startingResult = 0; - ResourceConsumption::DocumentUnitCounter docUnitsReturned; - - const int initialBufSize = - 512 + sizeof(QueryResult::Value) + FindCommon::kMaxBytesToReturnToClientAtOnce; - - BufBuilder bb(initialBufSize); - bb.skip(sizeof(QueryResult::Value)); - - // Check for spoofing of the ns such that it does not match the one originally there for the - // cursor. - uassert(ErrorCodes::Unauthorized, - str::stream() << "Requested getMore on namespace " << ns << ", but cursor " << cursorid - << " belongs to namespace " << cursorPin->nss().ns(), - nss == cursorPin->nss()); - - // A user can only call getMore on their own cursor. If there were multiple users authenticated - // when the cursor was created, then at least one of them must be authenticated in order to run - // getMore on the cursor. - uassert(ErrorCodes::Unauthorized, - str::stream() << "cursor id " << cursorid - << " was not created by the authenticated user", - AuthorizationSession::get(opCtx->getClient()) - ->isCoauthorizedWith(cursorPin->getAuthenticatedUsers())); - - *isCursorAuthorized = true; - - // Only used by the failpoints. - std::function<void()> dropAndReaquireReadLock = [&] { - // Make sure an interrupted operation does not prevent us from reacquiring the lock. - UninterruptibleLockGuard noInterrupt(opCtx->lockState()); - - readLock.reset(); - readLock.emplace(opCtx, nss); - }; - - // On early return, get rid of the cursor. - auto cursorFreer = makeGuard([&] { cursorPin.deleteUnderlying(); }); - - // If the 'waitAfterPinningCursorBeforeGetMoreBatch' fail point is enabled, set the - // 'msg' field of this operation's CurOp to signal that we've hit this point and then - // repeatedly release and re-acquire the collection readLock at regular intervals until - // the failpoint is released. This is done in order to avoid deadlocks caused by the - // pinned-cursor failpoints in this file (see SERVER-21997). - waitAfterPinningCursorBeforeGetMoreBatch.execute([&](const BSONObj& data) { - if (data["shouldNotdropLock"].booleanSafe()) { - dropAndReaquireReadLock = []() {}; - } - - CurOpFailpointHelpers::waitWhileFailPointEnabled(&waitAfterPinningCursorBeforeGetMoreBatch, - opCtx, - "waitAfterPinningCursorBeforeGetMoreBatch", - dropAndReaquireReadLock, - nss); - }); - - uassert(40548, - "OP_GET_MORE operations are not supported on tailable aggregations. Only clients " - "which support the getMore command can be used on tailable aggregations.", - readLock || !cursorPin->isAwaitData()); - uassert( - 31124, - str::stream() - << "OP_GET_MORE does not support cursors with a write concern other than the default." - " Use the getMore command instead. Write concern was: " - << cursorPin->getWriteConcernOptions().toBSON(), - cursorPin->getWriteConcernOptions().isImplicitDefaultWriteConcern()); - - // If the operation that spawned this cursor had a time limit set, apply leftover time to this - // getmore. - if (cursorPin->getLeftoverMaxTimeMicros() < Microseconds::max()) { - uassert(40136, - "Illegal attempt to set operation deadline within DBDirectClient", - !opCtx->getClient()->isInDirectClient()); - opCtx->setDeadlineAfterNowBy(cursorPin->getLeftoverMaxTimeMicros(), - ErrorCodes::MaxTimeMSExpired); - } - opCtx->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. - - // What number result are we starting at? Used to fill out the reply. - startingResult = cursorPin->nReturnedSoFar(); - - uint64_t notifierVersion = 0; - std::shared_ptr<CappedInsertNotifier> notifier; - if (cursorPin->isAwaitData()) { - invariant(readLock->getCollection()->isCapped()); - // Retrieve the notifier which we will wait on until new data arrives. We make sure to do - // this in the lock because once we drop the lock it is possible for the collection to - // become invalid. The notifier itself will outlive the collection if the collection is - // dropped, as we keep a shared_ptr to it. - notifier = readLock->getCollection()->getCappedInsertNotifier(); - - // Must get the version before we call generateBatch in case a write comes in after that - // call and before we call wait on the notifier. - notifierVersion = notifier->getVersion(); - } - - PlanExecutor* exec = cursorPin->getExecutor(); - exec->reattachToOperationContext(opCtx); - exec->restoreState(readLock ? &readLock->getCollection() : nullptr); - - auto planSummary = exec->getPlanExplainer().getPlanSummary(); - { - stdx::lock_guard<Client> lk(*opCtx->getClient()); - curOp.setPlanSummary_inlock(planSummary); - - // Ensure that the original query object is available in the slow query log, profiler and - // currentOp. Upconvert _query to resemble a getMore command, and set the original command - // or upconverted legacy query in the originatingCommand field. - curOp.setOpDescription_inlock(upconvertGetMoreEntry(nss, cursorid, ntoreturn)); - curOp.setOriginatingCommand_inlock(cursorPin->getOriginatingCommandObj()); - // Update the generic cursor in curOp. - curOp.setGenericCursor_inlock(cursorPin->toGenericCursor()); - } - - // If the 'failGetMoreAfterCursorCheckout' failpoint is enabled, throw an exception with the - // specified 'errorCode' value, or ErrorCodes::InternalError if 'errorCode' is omitted. - failGetMoreAfterCursorCheckout.executeIf( - [](const BSONObj& data) { - auto errorCode = (data["errorCode"] ? data["errorCode"].safeNumberLong() - : ErrorCodes::InternalError); - uasserted(errorCode, "Hit the 'failGetMoreAfterCursorCheckout' failpoint"); - }, - [&opCtx, &nss](const BSONObj& data) { - auto dataForFailCommand = - data.addField(BSON("failCommands" << BSON_ARRAY("getMore")).firstElement()); - auto* getMoreCommand = CommandHelpers::findCommand("getMore"); - return CommandHelpers::shouldActivateFailCommandFailPoint( - dataForFailCommand, nss, getMoreCommand, opCtx->getClient()); - }); - - PlanExecutor::ExecState state; - - // We report keysExamined and docsExamined to OpDebug for a given getMore operation. To obtain - // these values we need to take a diff of the pre-execution and post-execution metrics, as they - // accumulate over the course of a cursor's lifetime. - PlanSummaryStats preExecutionStats; - exec->getPlanExplainer().getSummaryStats(&preExecutionStats); - if (MONGO_unlikely(waitWithPinnedCursorDuringGetMoreBatch.shouldFail())) { - CurOpFailpointHelpers::waitWhileFailPointEnabled(&waitWithPinnedCursorDuringGetMoreBatch, - opCtx, - "waitWithPinnedCursorDuringGetMoreBatch", - nullptr); - } - - generateBatch(ntoreturn, cursorPin.getCursor(), &bb, &numResults, &docUnitsReturned, &state); - - // If this is an await data cursor, and we hit EOF without generating any results, then we block - // waiting for new data to arrive. - if (cursorPin->isAwaitData() && state == PlanExecutor::IS_EOF && numResults == 0) { - // Save the PlanExecutor and drop our locks. - exec->saveState(); - readLock.reset(); - - // Block waiting for data for up to 1 second. Time spent blocking is not counted towards the - // total operation latency. - curOp.pauseTimer(); - Seconds timeout(1); - notifier->waitUntil(notifierVersion, - opCtx->getServiceContext()->getPreciseClockSource()->now() + timeout); - notifier.reset(); - curOp.resumeTimer(); - - // Reacquiring locks. - readLock.emplace(opCtx, nss); - exec->restoreState(&readLock->getCollection()); - - // We woke up because either the timed_wait expired, or there was more data. Either way, - // attempt to generate another batch of results. - generateBatch( - ntoreturn, cursorPin.getCursor(), &bb, &numResults, &docUnitsReturned, &state); - } - - PlanSummaryStats postExecutionStats; - auto&& explainer = exec->getPlanExplainer(); - explainer.getSummaryStats(&postExecutionStats); - postExecutionStats.totalKeysExamined -= preExecutionStats.totalKeysExamined; - postExecutionStats.totalDocsExamined -= preExecutionStats.totalDocsExamined; - curOp.debug().setPlanSummaryMetrics(postExecutionStats); - - // We do not report 'execStats' for aggregation or other cursors with the 'kLocksInternally' - // policy, both in the original request and subsequent getMore. It would be useful to have this - // info for an aggregation, but the source PlanExecutor could be destroyed before we know if we - // need 'execStats' and we do not want to generate the stats eagerly for all operations due to - // cost. - if (cursorPin->getExecutor()->lockPolicy() != PlanExecutor::LockPolicy::kLocksInternally && - curOp.shouldDBProfile(opCtx)) { - auto&& [stats, _] = explainer.getWinningPlanStats(ExplainOptions::Verbosity::kExecStats); - - curOp.debug().execStats = std::move(stats); - } - - // Our two possible ClientCursorPin cleanup paths are: - // 1) If the cursor is not going to be saved, we call deleteUnderlying() on the pin. - // 2) If the cursor is going to be saved, we simply let the pin go out of scope. In this case, - // the pin's destructor will be invoked, which will call release() on the pin. Because our - // ClientCursorPin is declared after our lock is declared, this will happen under the lock if - // any locking was necessary. - if (!shouldSaveCursorGetMore(exec, cursorPin->isTailable())) { - // cc is now invalid, as is the executor - cursorid = 0; - curOp.debug().cursorExhausted = true; - - LOGV2_DEBUG(20910, - 5, - "getMore NOT saving client cursor", - "planExecutorState"_attr = PlanExecutor::stateToStr(state)); - } else { - cursorFreer.dismiss(); - // Continue caching the ClientCursor. - cursorPin->incNReturnedSoFar(numResults); - cursorPin->incNBatches(); - exec->saveState(); - exec->detachFromOperationContext(); - LOGV2_DEBUG(20911, - 5, - "getMore saving client cursor", - "planExecutorState"_attr = PlanExecutor::stateToStr(state)); - - // Set 'exhaust' if the client requested exhaust and the cursor is not exhausted. - *exhaust = opCtx->isExhaust(); - - // We assume that cursors created through a DBDirectClient are always used from their - // original OperationContext, so we do not need to move time to and from the cursor. - if (!opCtx->getClient()->isInDirectClient()) { - // If the getmore had a time limit, remaining time is "rolled over" back to the cursor - // (for use by future getmore ops). - cursorPin->setLeftoverMaxTimeMicros(opCtx->getRemainingMaxTimeMicros()); - } - } - - // We're about to unpin or delete the cursor as the ClientCursorPin goes out of scope. - // If the 'waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch' failpoint is active, we - // set the 'msg' field of this operation's CurOp to signal that we've hit this point and - // then spin until the failpoint is released. - if (MONGO_unlikely(waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch.shouldFail())) { - CurOpFailpointHelpers::waitWhileFailPointEnabled( - &waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch, - opCtx, - "waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch", - dropAndReaquireReadLock); - } - - // Increment this metric once the command succeeds and we know it will return documents. - auto& metricsCollector = ResourceConsumption::MetricsCollector::get(opCtx); - metricsCollector.incrementDocUnitsReturned(docUnitsReturned); - - QueryResult::View qr = bb.buf(); - qr.msgdata().setLen(bb.len()); - qr.msgdata().setOperation(opReply); - qr.setResultFlags(resultFlags); - qr.setCursorId(cursorid); - qr.setStartingFrom(startingResult); - qr.setNReturned(numResults); - LOGV2_DEBUG(20912, 5, "getMore returned results", "numResults"_attr = numResults); - return Message(bb.release()); -} - -bool runQuery(OperationContext* opCtx, - QueryMessage& q, - const NamespaceString& nss, - Message& result) { - CurOp& curOp = *CurOp::get(opCtx); - curOp.ensureStarted(); - - uassert(ErrorCodes::InvalidNamespace, - str::stream() << "Invalid ns [" << nss.ns() << "]", - nss.isValid()); - invariant(!nss.isCommand()); - - ResourceConsumption::ScopedMetricsCollector scopedMetrics(opCtx, nss.db().toString()); - - // Set CurOp information. - const auto upconvertedQuery = upconvertQueryEntry(q.query, nss, q.ntoreturn, q.ntoskip); - - // Extract the 'comment' parameter from the upconverted query, if it exists. - if (auto commentField = upconvertedQuery["comment"]) { - opCtx->setComment(commentField.wrap()); - } - - beginQueryOp(opCtx, nss, upconvertedQuery, q.ntoreturn, q.ntoskip); - - // Parse the qm into a CanonicalQuery. - const boost::intrusive_ptr<ExpressionContext> expCtx = - make_intrusive<ExpressionContext>(opCtx, nullptr /* collator */, nss); - auto cq = uassertStatusOKWithContext( - CanonicalQuery::canonicalize(opCtx, - q, - expCtx, - ExtensionsCallbackReal(opCtx, &nss), - MatchExpressionParser::kAllowAllSpecialFeatures), - "Can't canonicalize query"); - invariant(cq.get()); - - LOGV2_DEBUG(20913, 5, "Running query", "query"_attr = redact(cq->toString())); - LOGV2_DEBUG(20914, 2, "Running query", "query"_attr = redact(cq->toStringShort())); - - // Parse, canonicalize, plan, transcribe, and get a plan executor. - AutoGetCollectionForReadCommandMaybeLockFree collection( - opCtx, nss, AutoGetCollectionViewMode::kViewsForbidden); - - const bool isExhaust = (q.queryOptions & QueryOption_Exhaust) != 0; - opCtx->setExhaust(isExhaust); - - { - // Allow the query to run on secondaries if the read preference permits it. If no read - // preference was specified, allow the query to run iff slaveOk has been set. - const bool isSecondaryOk = (q.queryOptions & QueryOption_SecondaryOk) != 0; - const bool hasReadPref = q.query.hasField(query_request_helper::kWrappedReadPrefField); - const bool secondaryOk = hasReadPref - ? uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(q.query)) - .canRunOnSecondary() - : isSecondaryOk; - uassertStatusOK(repl::ReplicationCoordinator::get(opCtx)->checkCanServeReadsFor( - opCtx, nss, secondaryOk)); - } - - const FindCommandRequest& findCommand = cq->getFindCommandRequest(); - // Get the execution plan for the query. - constexpr auto verbosity = ExplainOptions::Verbosity::kExecAllPlans; - const bool isExplain = cq->getExplain(); - expCtx->explain = isExplain ? boost::make_optional(verbosity) : boost::none; - auto exec = - uassertStatusOK(getExecutorLegacyFind(opCtx, &collection.getCollection(), std::move(cq))); - - // If it's actually an explain, do the explain and return rather than falling through - // to the normal query execution loop. - if (isExplain) { - BufBuilder bb; - bb.skip(sizeof(QueryResult::Value)); - - BSONObjBuilder explainBob; - Explain::explainStages(exec.get(), - collection.getCollection(), - verbosity, - BSONObj(), - upconvertedQuery, - &explainBob); - - // Add the resulting object to the return buffer. - BSONObj explainObj = explainBob.obj(); - bb.appendBuf((void*)explainObj.objdata(), explainObj.objsize()); - - // Set query result fields. - QueryResult::View qr = bb.buf(); - qr.setResultFlagsToOk(); - qr.msgdata().setLen(bb.len()); - curOp.debug().responseLength = bb.len(); - qr.msgdata().setOperation(opReply); - qr.setCursorId(0); - qr.setStartingFrom(0); - qr.setNReturned(1); - result.setData(bb.release()); - return false; - } - - int maxTimeMS = findCommand.getMaxTimeMS() ? static_cast<int>(*findCommand.getMaxTimeMS()) : 0; - // Handle query option $maxTimeMS (not used with commands). - if (maxTimeMS > 0) { - uassert(40116, - "Illegal attempt to set operation deadline within DBDirectClient", - !opCtx->getClient()->isInDirectClient()); - opCtx->setDeadlineAfterNowBy(Milliseconds{maxTimeMS}, ErrorCodes::MaxTimeMSExpired); - } - opCtx->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. - - FindCommon::waitInFindBeforeMakingBatch(opCtx, *exec->getCanonicalQuery()); - - // Run the query. - // bb is used to hold query results - // this buffer should contain either requested documents per query or - // explain information, but not both - BufBuilder bb(FindCommon::kInitReplyBufferSize); - bb.skip(sizeof(QueryResult::Value)); - - // How many results have we obtained from the executor? - int numResults = 0; - ResourceConsumption::DocumentUnitCounter docUnitsReturned; - - BSONObj obj; - PlanExecutor::ExecState state; - - // Get summary info about which plan the executor is using. - { - stdx::lock_guard<Client> lk(*opCtx->getClient()); - curOp.setPlanSummary_inlock(exec->getPlanExplainer().getPlanSummary()); - } - - try { - while (PlanExecutor::ADVANCED == (state = exec->getNext(&obj, nullptr))) { - // If we can't fit this result inside the current batch, then we stash it for later. - if (!FindCommon::haveSpaceForNext(obj, numResults, bb.len())) { - exec->enqueue(obj); - break; - } - - // Add result to output buffer. - bb.appendBuf((void*)obj.objdata(), obj.objsize()); - - // Count the result. - ++numResults; - - docUnitsReturned.observeOne(obj.objsize()); - - if (FindCommon::enoughForFirstBatch(findCommand, numResults)) { - LOGV2_DEBUG(20915, - 5, - "Enough for first batch", - "wantMore"_attr = !findCommand.getSingleBatch(), - "numToReturn"_attr = findCommand.getNtoreturn().value_or(0), - "numResults"_attr = numResults); - break; - } - } - } catch (DBException& exception) { - auto&& explainer = exec->getPlanExplainer(); - auto&& [stats, _] = explainer.getWinningPlanStats(ExplainOptions::Verbosity::kExecStats); - LOGV2_ERROR(20919, - "Plan executor error during find", - "error"_attr = redact(exception.toStatus()), - "stats"_attr = redact(stats)); - - exception.addContext("Executor error during find"); - throw; - } - - // Fill out CurOp based on query results. If we have a cursorid, we will fill out CurOp with - // this cursorid later. - long long ccId = 0; - - if (shouldSaveCursor(opCtx, collection.getCollection(), state, exec.get())) { - // We won't use the executor until it's getMore'd. - exec->saveState(); - exec->detachFromOperationContext(); - - const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); - // Allocate a new ClientCursor and register it with the cursor manager. - ClientCursorPin pinnedCursor = CursorManager::get(opCtx)->registerCursor( - opCtx, - {std::move(exec), - nss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), - APIParameters::get(opCtx), - opCtx->getWriteConcern(), - readConcernArgs, - upconvertedQuery, - {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)}}); - ccId = pinnedCursor.getCursor()->cursorid(); - - LOGV2_DEBUG(20916, - 5, - "Caching executor after returning results", - "cursorId"_attr = ccId, - "numResults"_attr = numResults); - - // Set curOp.debug().exhaust if the client requested exhaust and the cursor is not - // exhausted. - if (opCtx->isExhaust()) { - curOp.debug().exhaust = true; - } - - pinnedCursor.getCursor()->setNReturnedSoFar(numResults); - pinnedCursor.getCursor()->incNBatches(); - - // We assume that cursors created through a DBDirectClient are always used from their - // original OperationContext, so we do not need to move time to and from the cursor. - if (!opCtx->getClient()->isInDirectClient()) { - // If the query had a time limit, remaining time is "rolled over" to the cursor (for - // use by future getmore ops). - pinnedCursor.getCursor()->setLeftoverMaxTimeMicros(opCtx->getRemainingMaxTimeMicros()); - } - - endQueryOp(opCtx, - collection.getCollection(), - *pinnedCursor.getCursor()->getExecutor(), - numResults, - ccId); - } else { - LOGV2_DEBUG( - 20917, 5, "Not caching executor but returning results", "numResults"_attr = numResults); - endQueryOp(opCtx, collection.getCollection(), *exec, numResults, ccId); - } - - // Increment this metric once it has succeeded and we know it will return documents. - auto& metricsCollector = ResourceConsumption::MetricsCollector::get(opCtx); - metricsCollector.incrementDocUnitsReturned(docUnitsReturned); - - // Fill out the output buffer's header. - QueryResult::View queryResultView = bb.buf(); - queryResultView.setCursorId(ccId); - queryResultView.setResultFlagsToOk(); - queryResultView.msgdata().setLen(bb.len()); - queryResultView.msgdata().setOperation(opReply); - queryResultView.setStartingFrom(0); - queryResultView.setNReturned(numResults); - - // Add the results from the query into the output buffer. - result.setData(bb.release()); - - // curOp.debug().exhaust is set above if the client requested exhaust and the cursor is not - // exhausted. - return curOp.debug().exhaust; -} - } // namespace mongo diff --git a/src/mongo/db/query/find.h b/src/mongo/db/query/find.h index 6913404cebb..65ff1ed01f5 100644 --- a/src/mongo/db/query/find.h +++ b/src/mongo/db/query/find.h @@ -84,20 +84,4 @@ void endQueryOp(OperationContext* opCtx, long long numResults, CursorId cursorId); -/** - * Called from the getMore entry point in ops/query.cpp. - * Returned buffer is the message to return to the client. - */ -Message getMore(OperationContext* opCtx, - const char* ns, - int ntoreturn, - long long cursorid, - bool* exhaust, - bool* isCursorAuthorized); - -/** - * Run the query 'q' and place the result in 'result'. Returns true if in exhaust mode. - */ -bool runQuery(OperationContext* opCtx, QueryMessage& q, const NamespaceString& ns, Message& result); - } // namespace mongo diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index cdb4375ffef..c62c497d7ba 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -1186,13 +1186,10 @@ inline bool isQuerySbeCompatible(OperationContext* opCtx, // ENSURE_SORTED stage. const bool doesNotNeedEnsureSorted = !cq->getFindCommandRequest().getNtoreturn(); - // OP_QUERY style find commands are not currently supported by SBE. - const bool isNotLegacy = !CurOp::get(opCtx)->isLegacyQuery(); - // Queries against a time-series collection are not currently supported by SBE. const bool isQueryNotAgainstTimeseriesCollection = !(cq->nss().isTimeseriesBucketsCollection()); return allExpressionsSupported && isNotCount && doesNotContainMetadataRequirements && - isNotLegacy && doesNotNeedEnsureSorted && isQueryNotAgainstTimeseriesCollection && + doesNotNeedEnsureSorted && isQueryNotAgainstTimeseriesCollection && doesNotSortOnMetaOrPathWithNumericComponents && isNotOplog; } } // namespace @@ -1245,17 +1242,6 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind opCtx, collection, std::move(canonicalQuery), yieldPolicy, plannerOptions); } -StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorLegacyFind( - OperationContext* opCtx, - const CollectionPtr* collection, - std::unique_ptr<CanonicalQuery> canonicalQuery) { - return _getExecutorFind(opCtx, - collection, - std::move(canonicalQuery), - PlanYieldPolicy::YieldPolicy::YIELD_AUTO, - QueryPlannerParams::DEFAULT); -} - namespace { /** diff --git a/src/mongo/db/query/get_executor.h b/src/mongo/db/query/get_executor.h index 26f420fd938..b3d8c5d332a 100644 --- a/src/mongo/db/query/get_executor.h +++ b/src/mongo/db/query/get_executor.h @@ -144,14 +144,6 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind size_t plannerOptions = QueryPlannerParams::DEFAULT); /** - * Returns a plan executor for a legacy OP_QUERY find. - */ -StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorLegacyFind( - OperationContext* opCtx, - const CollectionPtr* collection, - std::unique_ptr<CanonicalQuery> canonicalQuery); - -/** * If possible, turn the provided QuerySolution into a QuerySolution that uses a DistinctNode * to provide results for the distinct command. * diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index d64fb735b6e..12898e85ceb 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -111,7 +111,6 @@ namespace mongo { -MONGO_FAIL_POINT_DEFINE(rsStopGetMore); MONGO_FAIL_POINT_DEFINE(respondWithNotPrimaryInCommandDispatch); MONGO_FAIL_POINT_DEFINE(skipCheckingForNotPrimaryInCommandDispatch); MONGO_FAIL_POINT_DEFINE(sleepMillisAfterCommandExecutionBegins); @@ -225,66 +224,6 @@ struct HandleRequest { std::shared_ptr<ExecutionContext> executionContext; }; -void generateLegacyQueryErrorResponse(const AssertionException& exception, - const QueryMessage& queryMessage, - CurOp* curop, - Message* response) { - curop->debug().errInfo = exception.toStatus(); - - if (queryMessage.query.valid()) - LOGV2_OPTIONS(51777, - {logv2::LogComponent::kQuery}, - "Assertion {error} ns: {namespace} query: {query}", - "Assertion for valid query", - "error"_attr = exception, - "namespace"_attr = queryMessage.ns, - "query"_attr = redact(queryMessage.query)); - else - LOGV2_OPTIONS(51778, - {logv2::LogComponent::kQuery}, - "Assertion {error} ns: {namespace} query object is corrupt", - "Assertion for query with corrupted object", - "error"_attr = exception, - "namespace"_attr = queryMessage.ns); - - if (queryMessage.ntoskip || queryMessage.ntoreturn) { - LOGV2_OPTIONS(21952, - {logv2::LogComponent::kQuery}, - "Query's nToSkip = {nToSkip} and nToReturn = {nToReturn}", - "Assertion for query with nToSkip and/or nToReturn", - "nToSkip"_attr = queryMessage.ntoskip, - "nToReturn"_attr = queryMessage.ntoreturn); - } - - BSONObjBuilder err; - err.append("$err", exception.reason()); - err.append("code", exception.code()); - err.append("ok", 0.0); - auto const extraInfo = exception.extraInfo(); - if (extraInfo) { - extraInfo->serialize(&err); - } - BSONObj errObj = err.done(); - - invariant(!exception.isA<ErrorCategory::StaleShardVersionError>() && - exception.code() != ErrorCodes::StaleDbVersion); - - BufBuilder bb; - bb.skip(sizeof(QueryResult::Value)); - bb.appendBuf((void*)errObj.objdata(), errObj.objsize()); - - // TODO: call replyToQuery() from here instead of this!!! see dbmessage.h - QueryResult::View msgdata = bb.buf(); - QueryResult::View qr = msgdata; - qr.setResultFlags(ResultFlag_ErrSet); - qr.msgdata().setLen(bb.len()); - qr.msgdata().setOperation(opReply); - qr.setCursorId(0); - qr.setStartingFrom(0); - qr.setNReturned(1); - response->setData(bb.release()); -} - void registerError(OperationContext* opCtx, const Status& status) { LastError::get(opCtx->getClient()).setLastError(status.code(), status.reason()); CurOp::get(opCtx)->debug().errInfo = status; @@ -1989,132 +1928,6 @@ Future<DbResponse> receivedCommands(std::shared_ptr<HandleRequest::ExecutionCont .then([execContext]() mutable { return makeCommandResponse(std::move(execContext)); }); } -DbResponse receivedQuery(OperationContext* opCtx, - const NamespaceString& nss, - Client& c, - const Message& m, - const ServiceEntryPointCommon::Hooks& behaviors) { - invariant(!nss.isCommand()); - - // The legacy opcodes should be counted twice: as part of the overall opcodes' counts and on - // their own to highlight that they are being used. - globalOpCounters.gotQuery(); - globalOpCounters.gotQueryDeprecated(); - - if (!opCtx->getClient()->isInDirectClient()) { - ServerReadConcernMetrics::get(opCtx)->recordReadConcern(repl::ReadConcernArgs::get(opCtx), - false /* isTransaction */); - } - - DbMessage d(m); - QueryMessage q(d); - - CurOp& op = *CurOp::get(opCtx); - DbResponse dbResponse; - - try { - warnDeprecation(c, networkOpToString(m.operation())); - Client* client = opCtx->getClient(); - Status status = auth::checkAuthForFind(AuthorizationSession::get(client), nss, false); - audit::logQueryAuthzCheck(client, nss, q.query, status.code()); - uassertStatusOK(status); - - dbResponse.shouldRunAgainForExhaust = runQuery(opCtx, q, nss, dbResponse.response); - } catch (const AssertionException& e) { - dbResponse.response.reset(); - generateLegacyQueryErrorResponse(e, q, &op, &dbResponse.response); - } - - op.debug().responseLength = dbResponse.response.header().dataLen(); - return dbResponse; -} - -DbResponse receivedGetMore(OperationContext* opCtx, - const Message& m, - CurOp& curop, - bool* shouldLogOpDebug) { - // The legacy opcodes should be counted twice: as part of the overall opcodes' counts and on - // their own to highlight that they are being used. - globalOpCounters.gotGetMore(); - globalOpCounters.gotGetMoreDeprecated(); - DbMessage d(m); - - const char* ns = d.getns(); - int ntoreturn = d.pullInt(); - uassert( - 34419, str::stream() << "Invalid ntoreturn for OP_GET_MORE: " << ntoreturn, ntoreturn >= 0); - long long cursorid = d.pullInt64(); - - curop.debug().ntoreturn = ntoreturn; - curop.debug().cursorid = cursorid; - - { - stdx::lock_guard<Client> lk(*opCtx->getClient()); - CurOp::get(opCtx)->setNS_inlock(ns); - } - - bool exhaust = false; - bool isCursorAuthorized = false; - - DbResponse dbresponse; - try { - warnDeprecation(*opCtx->getClient(), networkOpToString(m.operation())); - const NamespaceString nsString(ns); - uassert(ErrorCodes::InvalidNamespace, - str::stream() << "Invalid ns [" << ns << "]", - nsString.isValid()); - - Status status = auth::checkAuthForGetMore( - AuthorizationSession::get(opCtx->getClient()), nsString, cursorid, false); - audit::logGetMoreAuthzCheck(opCtx->getClient(), nsString, cursorid, status.code()); - uassertStatusOK(status); - - while (MONGO_unlikely(rsStopGetMore.shouldFail())) { - sleepmillis(0); - } - - dbresponse.response = - getMore(opCtx, ns, ntoreturn, cursorid, &exhaust, &isCursorAuthorized); - } catch (AssertionException& e) { - if (isCursorAuthorized) { - // Make sure that killCursorGlobal does not throw an exception if it is interrupted. - UninterruptibleLockGuard noInterrupt(opCtx->lockState()); - - // If an error was thrown prior to auth checks, then the cursor should remain alive - // in order to prevent an unauthorized user from resulting in the death of a cursor. - // In other error cases, the cursor is dead and should be cleaned up. - // - // If killing the cursor fails, ignore the error and don't try again. The cursor - // should be reaped by the client cursor timeout thread. - CursorManager::get(opCtx)->killCursor(opCtx, cursorid).ignore(); - } - - BSONObjBuilder err; - err.append("$err", e.reason()); - err.append("code", e.code()); - BSONObj errObj = err.obj(); - - curop.debug().errInfo = e.toStatus(); - - dbresponse = replyToQuery(errObj, ResultFlag_ErrSet); - curop.debug().responseLength = dbresponse.response.header().dataLen(); - curop.debug().nreturned = 1; - *shouldLogOpDebug = true; - return dbresponse; - } - - curop.debug().responseLength = dbresponse.response.header().dataLen(); - auto queryResult = QueryResult::ConstView(dbresponse.response.buf()); - curop.debug().nreturned = queryResult.getNReturned(); - - if (exhaust) { - curop.debug().exhaust = true; - dbresponse.shouldRunAgainForExhaust = true; - } - - return dbresponse; -} - struct CommandOpRunner : HandleRequest::OpRunner { using HandleRequest::OpRunner::OpRunner; Future<DbResponse> run() override { @@ -2134,23 +1947,20 @@ struct SynchronousOpRunner : HandleRequest::OpRunner { struct QueryOpRunner : SynchronousOpRunner { using SynchronousOpRunner::SynchronousOpRunner; DbResponse runSync() override { - auto opCtx = executionContext->getOpCtx(); - opCtx->markKillOnClientDisconnect(); - return receivedQuery(opCtx, - executionContext->nsString(), - executionContext->client(), - executionContext->getMessage(), - *executionContext->behaviors); + invariant(!executionContext->nsString().isCommand()); + + globalOpCounters.gotQueryDeprecated(); + warnDeprecation(executionContext->client(), networkOpToString(dbQuery)); + return makeErrorResponseToDeprecatedOpQuery("OP_QUERY is no longer supported"); } }; struct GetMoreOpRunner : SynchronousOpRunner { using SynchronousOpRunner::SynchronousOpRunner; DbResponse runSync() override { - return receivedGetMore(executionContext->getOpCtx(), - executionContext->getMessage(), - executionContext->currentOp(), - &executionContext->forceLog); + globalOpCounters.gotGetMoreDeprecated(); + warnDeprecation(executionContext->client(), networkOpToString(dbGetMore)); + return makeErrorResponseToDeprecatedOpQuery("OP_GET_MORE is no longer supported"); } }; @@ -2220,7 +2030,9 @@ std::unique_ptr<HandleRequest::OpRunner> HandleRequest::makeOpRunner() { case dbQuery: if (!executionContext->nsString().isCommand()) return std::make_unique<QueryOpRunner>(this); - // FALLTHROUGH: it's a query containing a command + // FALLTHROUGH: it's a query containing a command. Ideally, we'd like to let through + // only hello|isMaster commands but at this point the command hasn't been parsed yet, so + // we don't know what it is. case dbMsg: return std::make_unique<CommandOpRunner>(this); case dbGetMore: diff --git a/src/mongo/db/service_entry_point_common.h b/src/mongo/db/service_entry_point_common.h index 53de36e4829..89d4cc529f6 100644 --- a/src/mongo/db/service_entry_point_common.h +++ b/src/mongo/db/service_entry_point_common.h @@ -40,7 +40,6 @@ namespace mongo { -extern FailPoint rsStopGetMore; extern FailPoint respondWithNotPrimaryInCommandDispatch; // When active, we won't check if we are primary in command dispatch. Activate this if you want to |